
/*! Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. */
(function (global) {

    (function (factory) {
        if (typeof define === 'function' && define.amd) {
            define(["./base"], factory);
        } else {
            global.msWriteProfilerMark && msWriteProfilerMark('WinJS.3.0 3.0.0.winjs.2014.10.2 ui.js,StartTM');
            factory(global.WinJS);
            global.msWriteProfilerMark && msWriteProfilerMark('WinJS.3.0 3.0.0.winjs.2014.10.2 ui.js,StopTM');
        }
    }(function (WinJS) {


var require = WinJS.Utilities._require;
var define = WinJS.Utilities._define;

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Virtualized Data Source
define('WinJS/VirtualizedDataSource/_VirtualizedDataSourceImpl',[
    'exports',
    '../Core/_Global',
    '../Core/_Base',
    '../Core/_BaseUtils',
    '../Core/_ErrorFromName',
    '../Core/_Events',
    '../Core/_Log',
    '../Core/_Resources',
    '../Core/_WriteProfilerMark',
    '../Promise',
    '../Scheduler',
    '../_Signal',
    '../Utilities/_UI'
    ], function listDataSourceInit(exports, _Global, _Base, _BaseUtils, _ErrorFromName, _Events, _Log, _Resources, _WriteProfilerMark, Promise, Scheduler, _Signal, _UI) {
    "use strict";

    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {

        VirtualizedDataSource: _Base.Namespace._lazy(function () {
            var MAX_BEGINREFRESH_COUNT = 100;
            var uniqueID = 1;

            var DataSourceStatus = _UI.DataSourceStatus,
            CountResult = _UI.CountResult,
            FetchError = _UI.FetchError,
            EditError = _UI.EditError;

            // Private statics

            var strings = {
                get listDataAdapterIsInvalid() { return "Invalid argument: listDataAdapter must be an object or an array."; },
                get indexIsInvalid() { return "Invalid argument: index must be a non-negative integer."; },
                get keyIsInvalid() { return "Invalid argument: key must be a string."; },
                get invalidItemReturned() { return "Error: data adapter returned item that is not an object."; },
                get invalidKeyReturned() { return "Error: data adapter returned item with undefined or null key."; },
                get invalidIndexReturned() { return "Error: data adapter should return undefined, null or a non-negative integer for the index."; },
                get invalidCountReturned() { return "Error: data adapter should return undefined, null, CountResult.unknown, or a non-negative integer for the count."; },
                get invalidRequestedCountReturned() { return "Error: data adapter should return CountResult.unknown, CountResult.failure, or a non-negative integer for the count."; },
                get refreshCycleIdentified() { return "refresh cycle found, likely data inconsistency"; },
            };

            var statusChangedEvent = "statuschanged";

            function _baseDataSourceConstructor(listDataAdapter, options) {
                /// <signature helpKeyword="WinJS.UI.VirtualizedDataSource._baseDataSourceConstructor">
                /// <summary locid="WinJS.UI.VirtualizedDataSource._baseDataSourceConstructor">
                /// Initializes the VirtualizedDataSource base class of a custom data source.
                /// </summary>
                /// <param name="listDataAdapter" type="IListDataAdapter" locid="WinJS.UI.VirtualizedDataSource._baseDataSourceConstructor_p:itemIndex">
                /// An object that implements IListDataAdapter and supplies data to the VirtualizedDataSource.
                /// </param>
                /// <param name="options" optional="true" type="Object" locid="WinJS.UI.VirtualizedDataSource._baseDataSourceConstructor_p:options">
                /// An object that contains properties that specify additonal options for the VirtualizedDataSource:
                ///
                /// cacheSize
                /// A Number that specifies minimum number of unrequested items to cache in case they are requested.
                ///
                /// The options parameter is optional.
                /// </param>
                /// </signature>

                // Private members

                /*jshint validthis: true */

                var listDataNotificationHandler,
                    cacheSize,
                    status,
                    statusPending,
                    statusChangePosted,
                    bindingMap,
                    nextListBindingID,
                    nextHandle,
                    nextListenerID,
                    getCountPromise,
                    resultsProcessed,
                    beginEditsCalled,
                    editsInProgress,
                    firstEditInProgress,
                    editQueue,
                    editsQueued,
                    synchronousEdit,
                    waitForRefresh,
                    dataNotificationsInProgress,
                    countDelta,
                    indexUpdateDeferred,
                    nextTempKey,
                    currentRefreshID,
                    fetchesPosted,
                    nextFetchID,
                    fetchesInProgress,
                    fetchCompleteCallbacks,
                    startMarker,
                    endMarker,
                    knownCount,
                    slotsStart,
                    slotListEnd,
                    slotsEnd,
                    handleMap,
                    keyMap,
                    indexMap,
                    releasedSlots,
                    lastSlotReleased,
                    reduceReleasedSlotCountPosted,
                    refreshRequested,
                    refreshInProgress,
                    refreshSignal,
                    refreshFetchesInProgress,
                    refreshItemsFetched,
                    refreshCount,
                    refreshStart,
                    refreshEnd,
                    keyFetchIDs,
                    refreshKeyMap,
                    refreshIndexMap,
                    deletedKeys,
                    synchronousProgress,
                    reentrantContinue,
                    synchronousRefresh,
                    reentrantRefresh;

                var beginRefreshCount = 0,
                    refreshHistory = new Array(100),
                    refreshHistoryPos = -1;

                var itemsFromKey,
                    itemsFromIndex,
                    itemsFromStart,
                    itemsFromEnd,
                    itemsFromDescription;

                if (listDataAdapter.itemsFromKey) {
                    itemsFromKey = function (fetchID, key, countBefore, countAfter, hints) {
                        var perfID = "fetchItemsFromKey id=" + fetchID + " key=" + key + " countBefore=" + countBefore + " countAfter=" + countAfter;
                        profilerMarkStart(perfID);
                        refreshHistory[++refreshHistoryPos % refreshHistory.length] = { kind: "itemsFromKey", key: key, countBefore: countBefore, countAfter: countAfter };
                        var result = listDataAdapter.itemsFromKey(key, countBefore, countAfter, hints);
                        profilerMarkEnd(perfID);
                        return result;
                    };
                }
                if (listDataAdapter.itemsFromIndex) {
                    itemsFromIndex = function (fetchID, index, countBefore, countAfter) {
                        var perfID = "fetchItemsFromIndex id=" + fetchID + " index=" + index + " countBefore=" + countBefore + " countAfter=" + countAfter;
                        profilerMarkStart(perfID);
                        refreshHistory[++refreshHistoryPos % refreshHistory.length] = { kind: "itemsFromIndex", index: index, countBefore: countBefore, countAfter: countAfter };
                        var result = listDataAdapter.itemsFromIndex(index, countBefore, countAfter);
                        profilerMarkEnd(perfID);
                        return result;
                    };
                }
                if (listDataAdapter.itemsFromStart) {
                    itemsFromStart = function (fetchID, count) {
                        var perfID = "fetchItemsFromStart id=" + fetchID + " count=" + count;
                        profilerMarkStart(perfID);
                        refreshHistory[++refreshHistoryPos % refreshHistory.length] = { kind: "itemsFromStart", count: count };
                        var result = listDataAdapter.itemsFromStart(count);
                        profilerMarkEnd(perfID);
                        return result;
                    };
                }
                if (listDataAdapter.itemsFromEnd) {
                    itemsFromEnd = function (fetchID, count) {
                        var perfID = "fetchItemsFromEnd id=" + fetchID + " count=" + count;
                        profilerMarkStart(perfID);
                        refreshHistory[++refreshHistoryPos % refreshHistory.length] = { kind: "itemsFromEnd", count: count };
                        var result = listDataAdapter.itemsFromEnd(count);
                        profilerMarkEnd(perfID);
                        return result;
                    };
                }
                if (listDataAdapter.itemsFromDescription) {
                    itemsFromDescription = function (fetchID, description, countBefore, countAfter) {
                        var perfID = "fetchItemsFromDescription id=" + fetchID + " desc=" + description + " countBefore=" + countBefore + " countAfter=" + countAfter;
                        profilerMarkStart(perfID);
                        refreshHistory[++refreshHistoryPos % refreshHistory.length] = { kind: "itemsFromDescription", description: description, countBefore: countBefore, countAfter: countAfter };
                        var result = listDataAdapter.itemsFromDescription(description, countBefore, countAfter);
                        profilerMarkEnd(perfID);
                        return result;
                    };
                }

                var dataSourceID = ++uniqueID;

                function profilerMarkStart(text) {
                    var message = "WinJS.UI.VirtualizedDataSource:" + dataSourceID + ":" + text + ",StartTM";
                    _WriteProfilerMark(message);
                    _Log.log && _Log.log(message, "winjs vds", "perf");
                }
                function profilerMarkEnd(text) {
                    var message = "WinJS.UI.VirtualizedDataSource:" + dataSourceID + ":" + text + ",StopTM";
                    _WriteProfilerMark(message);
                    _Log.log && _Log.log(message, "winjs vds", "perf");
                }

                function isNonNegativeNumber(n) {
                    return (typeof n === "number") && n >= 0;
                }

                function isNonNegativeInteger(n) {
                    return isNonNegativeNumber(n) && n === Math.floor(n);
                }

                function validateIndexReturned(index) {
                    // Ensure that index is always undefined or a non-negative integer
                    if (index === null) {
                        index = undefined;
                    } else if (index !== undefined && !isNonNegativeInteger(index)) {
                        throw new _ErrorFromName("WinJS.UI.ListDataSource.InvalidIndexReturned", strings.invalidIndexReturned);
                    }

                    return index;
                }

                function validateCountReturned(count) {
                    // Ensure that count is always undefined or a non-negative integer
                    if (count === null) {
                        count = undefined;
                    } else if (count !== undefined && !isNonNegativeInteger(count) && count !== CountResult.unknown) {
                        throw new _ErrorFromName("WinJS.UI.ListDataSource.InvalidCountReturned", strings.invalidCountReturned);
                    }

                    return count;
                }

                // Slot List

                function createSlot() {
                    var handle = (nextHandle++).toString(),
                        slotNew = {
                            handle: handle,
                            item: null,
                            itemNew: null,
                            fetchListeners: null,
                            cursorCount: 0,
                            bindingMap: null
                        };

                    // Deliberately not initialized:
                    //   - directFetchListeners

                    handleMap[handle] = slotNew;

                    return slotNew;
                }

                function createPrimarySlot() {
                    return createSlot();
                }

                function insertSlot(slot, slotNext) {
                    slot.prev = slotNext.prev;
                    slot.next = slotNext;

                    slot.prev.next = slot;
                    slotNext.prev = slot;
                }

                function removeSlot(slot) {
                    if (slot.lastInSequence) {
                        delete slot.lastInSequence;
                        slot.prev.lastInSequence = true;
                    }
                    if (slot.firstInSequence) {
                        delete slot.firstInSequence;
                        slot.next.firstInSequence = true;
                    }
                    slot.prev.next = slot.next;
                    slot.next.prev = slot.prev;
                }

                function sequenceStart(slot) {
                    while (!slot.firstInSequence) {
                        slot = slot.prev;
                    }

                    return slot;
                }

                function sequenceEnd(slot) {
                    while (!slot.lastInSequence) {
                        slot = slot.next;
                    }

                    return slot;
                }

                // Does a little careful surgery to the slot sequence from slotFirst to slotLast before slotNext
                function moveSequenceBefore(slotNext, slotFirst, slotLast) {
                    slotFirst.prev.next = slotLast.next;
                    slotLast.next.prev = slotFirst.prev;

                    slotFirst.prev = slotNext.prev;
                    slotLast.next = slotNext;

                    slotFirst.prev.next = slotFirst;
                    slotNext.prev = slotLast;

                    return true;
                }

                // Does a little careful surgery to the slot sequence from slotFirst to slotLast after slotPrev
                function moveSequenceAfter(slotPrev, slotFirst, slotLast) {
                    slotFirst.prev.next = slotLast.next;
                    slotLast.next.prev = slotFirst.prev;

                    slotFirst.prev = slotPrev;
                    slotLast.next = slotPrev.next;

                    slotPrev.next = slotFirst;
                    slotLast.next.prev = slotLast;

                    return true;
                }

                function mergeSequences(slotPrev) {
                    delete slotPrev.lastInSequence;
                    delete slotPrev.next.firstInSequence;
                }

                function splitSequence(slotPrev) {
                    var slotNext = slotPrev.next;

                    slotPrev.lastInSequence = true;
                    slotNext.firstInSequence = true;

                    if (slotNext === slotListEnd) {
                        // Clear slotListEnd's index, as that's now unknown
                        changeSlotIndex(slotListEnd, undefined);
                    }
                }

                // Inserts a slot in the middle of a sequence or between sequences.  If the latter, mergeWithPrev and mergeWithNext
                // parameters specify whether to merge the slot with the previous sequence, or next, or neither.
                function insertAndMergeSlot(slot, slotNext, mergeWithPrev, mergeWithNext) {
                    insertSlot(slot, slotNext);

                    var slotPrev = slot.prev;

                    if (slotPrev.lastInSequence) {
                        if (mergeWithPrev) {
                            delete slotPrev.lastInSequence;
                        } else {
                            slot.firstInSequence = true;
                        }

                        if (mergeWithNext) {
                            delete slotNext.firstInSequence;
                        } else {
                            slot.lastInSequence = true;
                        }
                    }
                }

                // Keys and Indices

                function setSlotKey(slot, key) {
                    slot.key = key;

                    // Add the slot to the keyMap, so it is possible to quickly find the slot given its key
                    keyMap[slot.key] = slot;
                }

                function setSlotIndex(slot, index, indexMapForSlot) {
                    // Tolerate NaN, so clients can pass (undefined - 1) or (undefined + 1)
                    if (+index === index) {
                        slot.index = index;

                        // Add the slot to the indexMap, so it is possible to quickly find the slot given its index
                        indexMapForSlot[index] = slot;

                        if (!indexUpdateDeferred) {
                            // See if any sequences should be merged
                            if (slot.firstInSequence && slot.prev && slot.prev.index === index - 1) {
                                mergeSequences(slot.prev);
                            }
                            if (slot.lastInSequence && slot.next && slot.next.index === index + 1) {
                                mergeSequences(slot);
                            }
                        }
                    }
                }

                // Creates a new slot and adds it to the slot list before slotNext
                function createAndAddSlot(slotNext, indexMapForSlot) {
                    var slotNew = (indexMapForSlot === indexMap ? createPrimarySlot() : createSlot());

                    insertSlot(slotNew, slotNext);

                    return slotNew;
                }

                function createSlotSequence(slotNext, index, indexMapForSlot) {
                    var slotNew = createAndAddSlot(slotNext, indexMapForSlot);

                    slotNew.firstInSequence = true;
                    slotNew.lastInSequence = true;

                    setSlotIndex(slotNew, index, indexMapForSlot);

                    return slotNew;
                }

                function createPrimarySlotSequence(slotNext, index) {
                    return createSlotSequence(slotNext, index, indexMap);
                }

                function addSlotBefore(slotNext, indexMapForSlot) {
                    var slotNew = createAndAddSlot(slotNext, indexMapForSlot);
                    delete slotNext.firstInSequence;

                    // See if we've bumped into the previous sequence
                    if (slotNew.prev.index === slotNew.index - 1) {
                        delete slotNew.prev.lastInSequence;
                    } else {
                        slotNew.firstInSequence = true;
                    }

                    setSlotIndex(slotNew, slotNext.index - 1, indexMapForSlot);

                    return slotNew;
                }

                function addSlotAfter(slotPrev, indexMapForSlot) {
                    var slotNew = createAndAddSlot(slotPrev.next, indexMapForSlot);
                    delete slotPrev.lastInSequence;

                    // See if we've bumped into the next sequence
                    if (slotNew.next.index === slotNew.index + 1) {
                        delete slotNew.next.firstInSequence;
                    } else {
                        slotNew.lastInSequence = true;
                    }

                    setSlotIndex(slotNew, slotPrev.index + 1, indexMapForSlot);

                    return slotNew;
                }

                function reinsertSlot(slot, slotNext, mergeWithPrev, mergeWithNext) {
                    insertAndMergeSlot(slot, slotNext, mergeWithPrev, mergeWithNext);
                    keyMap[slot.key] = slot;
                    if (slot.index !== undefined) {
                        indexMap[slot.index] = slot;
                    }
                }

                function removeSlotPermanently(slot) {
                    removeSlot(slot);

                    if (slot.key) {
                        delete keyMap[slot.key];
                    }
                    if (slot.index !== undefined && indexMap[slot.index] === slot) {
                        delete indexMap[slot.index];
                    }

                    var bindingMap = slot.bindingMap;
                    for (var listBindingID in bindingMap) {
                        var handle = bindingMap[listBindingID].handle;
                        if (handle && handleMap[handle] === slot) {
                            delete handleMap[handle];
                        }
                    }

                    // Invalidating the slot's handle marks it as deleted
                    if (handleMap[slot.handle] === slot) {
                        delete handleMap[slot.handle];
                    }
                }

                function slotPermanentlyRemoved(slot) {
                    return !handleMap[slot.handle];
                }

                function successorFromIndex(index, indexMapForSlot, listStart, listEnd, skipPreviousIndex) {
                    // Try the previous index
                    var slotNext = (skipPreviousIndex ? null : indexMapForSlot[index - 1]);
                    if (slotNext && (slotNext.next !== listEnd || listEnd.firstInSequence)) {
                        // We want the successor
                        slotNext = slotNext.next;
                    } else {
                        // Try the next index
                        slotNext = indexMapForSlot[index + 1];
                        if (!slotNext) {
                            // Resort to a linear search
                            slotNext = listStart.next;
                            var lastSequenceStart;
                            while (true) {
                                if (slotNext.firstInSequence) {
                                    lastSequenceStart = slotNext;
                                }

                                if (!(index >= slotNext.index) || slotNext === listEnd) {
                                    break;
                                }

                                slotNext = slotNext.next;
                            }

                            if (slotNext === listEnd && !listEnd.firstInSequence) {
                                // Return the last insertion point between sequences, or undefined if none
                                slotNext = (lastSequenceStart && lastSequenceStart.index === undefined ? lastSequenceStart : undefined);
                            }
                        }
                    }

                    return slotNext;
                }

                // Slot Items

                function isPlaceholder(slot) {
                    return !slot.item && !slot.itemNew && slot !== slotListEnd;
                }

                function defineHandleProperty(item, handle) {
                    Object.defineProperty(item, "handle", {
                        value: handle,
                        writable: false,
                        enumerable: false,
                        configurable: true
                    });
                }

                function defineCommonItemProperties(item, slot, handle) {
                    defineHandleProperty(item, handle);

                    Object.defineProperty(item, "index", {
                        get: function () {
                            while (slot.slotMergedWith) {
                                slot = slot.slotMergedWith;
                            }

                            return slot.index;
                        },
                        enumerable: false,
                        configurable: true
                    });
                }

                function validateData(data) {
                    if (data === undefined) {
                        return data;
                    } else {
                        // Convert the data object to JSON to enforce the constraints we want.  For example, we don't want
                        // functions, arrays with extra properties, DOM objects, cyclic or acyclic graphs, or undefined values.
                        var dataValidated = JSON.stringify(data);

                        if (dataValidated === undefined) {
                            throw new _ErrorFromName("WinJS.UI.ListDataSource.ObjectIsNotValidJson", strings.objectIsNotValidJson);
                        }

                        return dataValidated;
                    }
                }

                function itemSignature(item) {
                    return (
                        listDataAdapter.itemSignature ?
                            listDataAdapter.itemSignature(item.data) :
                            validateData(item.data)
                    );
                }

                function prepareSlotItem(slot) {
                    var item = slot.itemNew;
                    slot.itemNew = null;

                    if (item) {
                        item = Object.create(item);
                        defineCommonItemProperties(item, slot, slot.handle);

                        if (!listDataAdapter.compareByIdentity) {
                            // Store the item signature or a stringified copy of the data for comparison later
                            slot.signature = itemSignature(item);
                        }
                    }

                    slot.item = item;

                    delete slot.indexRequested;
                    delete slot.keyRequested;
                }

                // Slot Caching

                function slotRetained(slot) {
                    return slot.bindingMap || slot.cursorCount > 0;
                }

                function slotRequested(slot) {
                    return slotRetained(slot) || slot.fetchListeners || slot.directFetchListeners;
                }

                function slotLive(slot) {
                    return slotRequested(slot) || (!slot.firstInSequence && slotRetained(slot.prev)) || (!slot.lastInSequence && slotRetained(slot.next)) ||
                        (!itemsFromIndex && (
                            (!slot.firstInSequence && slot.prev !== slotsStart && !(slot.prev.item || slot.prev.itemNew)) |
                            (!slot.lastInSequence && slot.next !== slotListEnd && !(slot.next.item || slot.next.itemNew))
                        ));
                }

                function deleteUnnecessarySlot(slot) {
                    splitSequence(slot);
                    removeSlotPermanently(slot);
                }

                function reduceReleasedSlotCount() {
                    // Must not release slots while edits are queued, as undo queue might refer to them
                    if (!editsQueued) {
                        // If lastSlotReleased is no longer valid, use the end of the list instead
                        if (!lastSlotReleased || slotPermanentlyRemoved(lastSlotReleased)) {
                            lastSlotReleased = slotListEnd.prev;
                        }

                        // Now use the simple heuristic of walking outwards in both directions from lastSlotReleased until the
                        // desired cache size is reached, then removing everything else.
                        var slotPrev = lastSlotReleased.prev,
                            slotNext = lastSlotReleased.next,
                            releasedSlotsFound = 0;

                        var considerDeletingSlot = function (slotToDelete) {
                            if (slotToDelete !== slotListEnd && !slotLive(slotToDelete)) {
                                if (releasedSlotsFound <= cacheSize) {
                                    releasedSlotsFound++;
                                } else {
                                    deleteUnnecessarySlot(slotToDelete);
                                }
                            }
                        };

                        while (slotPrev || slotNext) {
                            if (slotPrev) {
                                var slotPrevToDelete = slotPrev;
                                slotPrev = slotPrevToDelete.prev;
                                if (slotPrevToDelete !== slotsStart) {
                                    considerDeletingSlot(slotPrevToDelete);
                                }
                            }
                            if (slotNext) {
                                var slotNextToDelete = slotNext;
                                slotNext = slotNextToDelete.next;
                                if (slotNextToDelete !== slotsEnd) {
                                    considerDeletingSlot(slotNextToDelete);
                                }
                            }
                        }

                        // Reset the count to zero, so this method is only called periodically
                        releasedSlots = 0;
                    }
                }

                function releaseSlotIfUnrequested(slot) {
                    if (!slotRequested(slot)) {

                        releasedSlots++;

                        // Must not release slots while edits are queued, as undo queue might refer to them.  If a refresh is in
                        // progress, retain all slots, just in case the user re-requests some of them before the refresh completes.
                        if (!editsQueued && !refreshInProgress) {
                            // Track which slot was released most recently
                            lastSlotReleased = slot;

                            // See if the number of released slots has exceeded the cache size.  In practice there will be more
                            // live slots than retained slots, so this is just a heuristic to periodically shrink the cache.
                            if (releasedSlots > cacheSize && !reduceReleasedSlotCountPosted) {
                                reduceReleasedSlotCountPosted = true;
                                Scheduler.schedule(function VDS_async_releaseSlotIfUnrequested() {
                                    reduceReleasedSlotCountPosted = false;
                                    reduceReleasedSlotCount();
                                }, Scheduler.Priority.idle, null, "WinJS.UI.VirtualizedDataSource.releaseSlotIfUnrequested");
                            }
                        }
                    }
                }

                // Notifications

                function forEachBindingRecord(callback) {
                    for (var listBindingID in bindingMap) {
                        callback(bindingMap[listBindingID]);
                    }
                }

                function forEachBindingRecordOfSlot(slot, callback) {
                    for (var listBindingID in slot.bindingMap) {
                        callback(slot.bindingMap[listBindingID].bindingRecord, listBindingID);
                    }
                }

                function handlerToNotify(bindingRecord) {
                    if (!bindingRecord.notificationsSent) {
                        bindingRecord.notificationsSent = true;

                        if (bindingRecord.notificationHandler.beginNotifications) {
                            bindingRecord.notificationHandler.beginNotifications();
                        }
                    }
                    return bindingRecord.notificationHandler;
                }

                function finishNotifications() {
                    if (!editsInProgress && !dataNotificationsInProgress) {
                        forEachBindingRecord(function (bindingRecord) {
                            if (bindingRecord.notificationsSent) {
                                bindingRecord.notificationsSent = false;

                                if (bindingRecord.notificationHandler.endNotifications) {
                                    bindingRecord.notificationHandler.endNotifications();
                                }
                            }
                        });
                    }
                }

                function handleForBinding(slot, listBindingID) {
                    var bindingMap = slot.bindingMap;
                    if (bindingMap) {
                        var slotBinding = bindingMap[listBindingID];
                        if (slotBinding) {
                            var handle = slotBinding.handle;
                            if (handle) {
                                return handle;
                            }
                        }
                    }
                    return slot.handle;
                }

                function itemForBinding(item, handle) {
                    if (item && item.handle !== handle) {
                        item = Object.create(item);
                        defineHandleProperty(item, handle);
                    }
                    return item;
                }

                function changeCount(count) {
                    var oldCount = knownCount;
                    knownCount = count;

                    forEachBindingRecord(function (bindingRecord) {
                        if (bindingRecord.notificationHandler && bindingRecord.notificationHandler.countChanged) {
                            handlerToNotify(bindingRecord).countChanged(knownCount, oldCount);
                        }
                    });
                }

                function sendIndexChangedNotifications(slot, indexOld) {
                    forEachBindingRecordOfSlot(slot, function (bindingRecord, listBindingID) {
                        if (bindingRecord.notificationHandler.indexChanged) {
                            handlerToNotify(bindingRecord).indexChanged(handleForBinding(slot, listBindingID), slot.index, indexOld);
                        }
                    });
                }

                function changeSlotIndex(slot, index) {
                    var indexOld = slot.index;

                    if (indexOld !== undefined && indexMap[indexOld] === slot) {
                        // Remove the slot's old index from the indexMap
                        delete indexMap[indexOld];
                    }

                    // Tolerate NaN, so clients can pass (undefined - 1) or (undefined + 1)
                    if (+index === index) {
                        setSlotIndex(slot, index, indexMap);
                    } else if (+indexOld === indexOld) {
                        delete slot.index;
                    } else {
                        // If neither the new index or the old index is defined then there was no index changed.
                        return;
                    }

                    sendIndexChangedNotifications(slot, indexOld);
                }

                function insertionNotificationRecipients(slot, slotPrev, slotNext, mergeWithPrev, mergeWithNext) {
                    var bindingMapRecipients = {};

                    // Start with the intersection of the bindings for the two adjacent slots
                    if ((mergeWithPrev || !slotPrev.lastInSequence) && (mergeWithNext || !slotNext.firstInSequence)) {
                        if (slotPrev === slotsStart) {
                            if (slotNext === slotListEnd) {
                                // Special case: if the list was empty, broadcast the insertion to all ListBindings with
                                // notification handlers.
                                for (var listBindingID in bindingMap) {
                                    bindingMapRecipients[listBindingID] = bindingMap[listBindingID];
                                }
                            } else {
                                // Include every binding on the next slot
                                for (var listBindingID in slotNext.bindingMap) {
                                    bindingMapRecipients[listBindingID] = bindingMap[listBindingID];
                                }
                            }
                        } else if (slotNext === slotListEnd || slotNext.bindingMap) {
                            for (var listBindingID in slotPrev.bindingMap) {
                                if (slotNext === slotListEnd || slotNext.bindingMap[listBindingID]) {
                                    bindingMapRecipients[listBindingID] = bindingMap[listBindingID];
                                }
                            }
                        }
                    }

                    // Use the union of that result with the bindings for the slot being inserted
                    for (var listBindingID in slot.bindingMap) {
                        bindingMapRecipients[listBindingID] = bindingMap[listBindingID];
                    }

                    return bindingMapRecipients;
                }

                function sendInsertedNotification(slot) {
                    var slotPrev = slot.prev,
                        slotNext = slot.next,
                        bindingMapRecipients = insertionNotificationRecipients(slot, slotPrev, slotNext),
                        listBindingID;

                    for (listBindingID in bindingMapRecipients) {
                        var bindingRecord = bindingMapRecipients[listBindingID];
                        if (bindingRecord.notificationHandler) {
                            handlerToNotify(bindingRecord).inserted(bindingRecord.itemPromiseFromKnownSlot(slot),
                                slotPrev.lastInSequence || slotPrev === slotsStart ? null : handleForBinding(slotPrev, listBindingID),
                                slotNext.firstInSequence || slotNext === slotListEnd ? null : handleForBinding(slotNext, listBindingID)
                            );
                        }
                    }
                }

                function changeSlot(slot) {
                    var itemOld = slot.item;
                    prepareSlotItem(slot);

                    forEachBindingRecordOfSlot(slot, function (bindingRecord, listBindingID) {
                        var handle = handleForBinding(slot, listBindingID);
                        handlerToNotify(bindingRecord).changed(itemForBinding(slot.item, handle), itemForBinding(itemOld, handle));
                    });
                }

                function moveSlot(slot, slotMoveBefore, mergeWithPrev, mergeWithNext, skipNotifications) {
                    var slotMoveAfter = slotMoveBefore.prev,
                        listBindingID;

                    // If the slot is being moved before or after itself, adjust slotMoveAfter or slotMoveBefore accordingly. If
                    // nothing is going to change in the slot list, don't send a notification.
                    if (slotMoveBefore === slot) {
                        if (!slot.firstInSequence || !mergeWithPrev) {
                            return;
                        }
                        slotMoveBefore = slot.next;
                    } else if (slotMoveAfter === slot) {
                        if (!slot.lastInSequence || !mergeWithNext) {
                            return;
                        }
                        slotMoveAfter = slot.prev;
                    }

                    if (!skipNotifications) {
                        // Determine which bindings to notify

                        var bindingMapRecipients = insertionNotificationRecipients(slot, slotMoveAfter, slotMoveBefore, mergeWithPrev, mergeWithNext);

                        // Send the notification before the move
                        for (listBindingID in bindingMapRecipients) {
                            var bindingRecord = bindingMapRecipients[listBindingID];
                            handlerToNotify(bindingRecord).moved(bindingRecord.itemPromiseFromKnownSlot(slot),
                                ((slotMoveAfter.lastInSequence || slotMoveAfter === slot.prev) && !mergeWithPrev) || slotMoveAfter === slotsStart ? null : handleForBinding(slotMoveAfter, listBindingID),
                                ((slotMoveBefore.firstInSequence || slotMoveBefore === slot.next) && !mergeWithNext) || slotMoveBefore === slotListEnd ? null : handleForBinding(slotMoveBefore, listBindingID)
                            );
                        }

                        // If a ListBinding cursor is at the slot that's moving, adjust the cursor
                        forEachBindingRecord(function (bindingRecord) {
                            bindingRecord.adjustCurrentSlot(slot);
                        });
                    }

                    removeSlot(slot);
                    insertAndMergeSlot(slot, slotMoveBefore, mergeWithPrev, mergeWithNext);
                }

                function deleteSlot(slot, mirage) {
                    completeFetchPromises(slot, true);

                    forEachBindingRecordOfSlot(slot, function (bindingRecord, listBindingID) {
                        handlerToNotify(bindingRecord).removed(handleForBinding(slot, listBindingID), mirage);
                    });

                    // If a ListBinding cursor is at the slot that's being removed, adjust the cursor
                    forEachBindingRecord(function (bindingRecord) {
                        bindingRecord.adjustCurrentSlot(slot);
                    });

                    removeSlotPermanently(slot);
                }

                function deleteMirageSequence(slot) {
                    // Remove the slots in order

                    while (!slot.firstInSequence) {
                        slot = slot.prev;
                    }

                    var last;
                    do {
                        last = slot.lastInSequence;

                        var slotNext = slot.next;
                        deleteSlot(slot, true);
                        slot = slotNext;
                    } while (!last);
                }

                // Deferred Index Updates

                // Returns the index of the slot taking into account any outstanding index updates
                function adjustedIndex(slot) {
                    var undefinedIndex;

                    if (!slot) {
                        return undefinedIndex;
                    }

                    var delta = 0;
                    while (!slot.firstInSequence) {
                        delta++;
                        slot = slot.prev;
                    }

                    return (
                        typeof slot.indexNew === "number" ?
                            slot.indexNew + delta :
                        typeof slot.index === "number" ?
                            slot.index + delta :
                            undefinedIndex
                    );
                }

                // Updates the new index of the first slot in each sequence after the given slot
                function updateNewIndicesAfterSlot(slot, indexDelta) {
                    // Adjust all the indexNews after this slot
                    for (slot = slot.next; slot; slot = slot.next) {
                        if (slot.firstInSequence) {
                            var indexNew = (slot.indexNew !== undefined ? slot.indexNew : slot.index);
                            if (indexNew !== undefined) {
                                slot.indexNew = indexNew + indexDelta;
                            }
                        }
                    }

                    // Adjust the overall count
                    countDelta += indexDelta;

                    indexUpdateDeferred = true;

                    // Increment currentRefreshID so any outstanding fetches don't cause trouble.  If a refresh is in progress,
                    // restart it (which will also increment currentRefreshID).
                    if (refreshInProgress) {
                        beginRefresh();
                    } else {
                        currentRefreshID++;
                    }
                }

                // Updates the new index of the given slot if necessary, and all subsequent new indices
                function updateNewIndices(slot, indexDelta) {
                    // If this slot is at the start of a sequence, transfer the indexNew
                    if (slot.firstInSequence) {
                        var indexNew;

                        if (indexDelta < 0) {
                            // The given slot is about to be removed
                            indexNew = slot.indexNew;
                            if (indexNew !== undefined) {
                                delete slot.indexNew;
                            } else {
                                indexNew = slot.index;
                            }

                            if (!slot.lastInSequence) {
                                // Update the next slot now
                                slot = slot.next;
                                if (indexNew !== undefined) {
                                    slot.indexNew = indexNew;
                                }
                            }
                        } else {
                            // The given slot was just inserted
                            if (!slot.lastInSequence) {
                                var slotNext = slot.next;

                                indexNew = slotNext.indexNew;
                                if (indexNew !== undefined) {
                                    delete slotNext.indexNew;
                                } else {
                                    indexNew = slotNext.index;
                                }

                                if (indexNew !== undefined) {
                                    slot.indexNew = indexNew;
                                }
                            }
                        }
                    }

                    updateNewIndicesAfterSlot(slot, indexDelta);
                }

                // Updates the new index of the first slot in each sequence after the given new index
                function updateNewIndicesFromIndex(index, indexDelta) {
                    for (var slot = slotsStart; slot !== slotListEnd; slot = slot.next) {
                        var indexNew = slot.indexNew;

                        if (indexNew !== undefined && index <= indexNew) {
                            updateNewIndicesAfterSlot(slot, indexDelta);
                            break;
                        }
                    }
                }

                // Adjust the indices of all slots to be consistent with any indexNew properties, and strip off the indexNews
                function updateIndices() {
                    var slot,
                        slotFirstInSequence,
                        indexNew;

                    for (slot = slotsStart; ; slot = slot.next) {
                        if (slot.firstInSequence) {
                            slotFirstInSequence = slot;
                            if (slot.indexNew !== undefined) {
                                indexNew = slot.indexNew;
                                delete slot.indexNew;
                                if (isNaN(indexNew)) {
                                    break;
                                }
                            } else {
                                indexNew = slot.index;
                            }

                            // See if this sequence should be merged with the previous one
                            if (slot !== slotsStart && slot.prev.index === indexNew - 1) {
                                mergeSequences(slot.prev);
                            }
                        }

                        if (slot.lastInSequence) {
                            var index = indexNew;
                            for (var slotUpdate = slotFirstInSequence; slotUpdate !== slot.next; slotUpdate = slotUpdate.next) {
                                if (index !== slotUpdate.index) {
                                    changeSlotIndex(slotUpdate, index);
                                }
                                if (+index === index) {
                                    index++;
                                }
                            }
                        }

                        if (slot === slotListEnd) {
                            break;
                        }
                    }

                    // Clear any indices on slots that were moved adjacent to slots without indices
                    for (; slot !== slotsEnd; slot = slot.next) {
                        if (slot.index !== undefined && slot !== slotListEnd) {
                            changeSlotIndex(slot, undefined);
                        }
                    }

                    indexUpdateDeferred = false;

                    if (countDelta && +knownCount === knownCount) {
                        if (getCountPromise) {
                            getCountPromise.reset();
                        } else {
                            changeCount(knownCount + countDelta);
                        }

                        countDelta = 0;
                    }
                }

                // Fetch Promises

                function createFetchPromise(slot, listenersProperty, listenerID, listBindingID, onComplete) {
                    if (slot.item) {
                        return new Promise(function (complete) {
                            if (onComplete) {
                                onComplete(complete, slot.item);
                            } else {
                                complete(slot.item);
                            }
                        });
                    } else {
                        var listener = {
                            listBindingID: listBindingID,
                            retained: false
                        };

                        if (!slot[listenersProperty]) {
                            slot[listenersProperty] = {};
                        }
                        slot[listenersProperty][listenerID] = listener;

                        listener.promise = new Promise(function (complete, error) {
                            listener.complete = (onComplete ? function (item) {
                                onComplete(complete, item);
                            } : complete);
                            listener.error = error;
                        }, function () {
                            // By now the slot might have been merged with another

                            while (slot.slotMergedWith) {
                                slot = slot.slotMergedWith;
                            }

                            var fetchListeners = slot[listenersProperty];
                            if (fetchListeners) {
                                delete fetchListeners[listenerID];

                                // See if there are any other listeners
                                if (Object.keys(fetchListeners).length > 0) {
                                    return;
                                }
                                delete slot[listenersProperty];
                            }
                            releaseSlotIfUnrequested(slot);
                        });

                        return listener.promise;
                    }
                }

                function completePromises(item, listeners) {
                    for (var listenerID in listeners) {
                        listeners[listenerID].complete(item);
                    }
                }

                function completeFetchPromises(slot, completeSynchronously) {
                    var fetchListeners = slot.fetchListeners,
                        directFetchListeners = slot.directFetchListeners;

                    if (fetchListeners || directFetchListeners) {
                        prepareSlotItem(slot);

                        // By default, complete asynchronously to minimize reentrancy

                        var item = slot.item;

                        var completeOrQueuePromises = function (listeners) {
                            if (completeSynchronously) {
                                completePromises(item, listeners);
                            } else {
                                fetchCompleteCallbacks.push(function () {
                                    completePromises(item, listeners);
                                });
                            }
                        };

                        if (directFetchListeners) {
                            slot.directFetchListeners = null;
                            completeOrQueuePromises(directFetchListeners);
                        }

                        if (fetchListeners) {
                            slot.fetchListeners = null;
                            completeOrQueuePromises(fetchListeners);
                        }

                        releaseSlotIfUnrequested(slot);
                    }
                }

                function callFetchCompleteCallbacks() {
                    var callbacks = fetchCompleteCallbacks;

                    // Clear fetchCompleteCallbacks first to avoid reentrancy problems
                    fetchCompleteCallbacks = [];

                    for (var i = 0, len = callbacks.length; i < len; i++) {
                        callbacks[i]();
                    }
                }

                function returnDirectFetchError(slot, error) {
                    var directFetchListeners = slot.directFetchListeners;
                    if (directFetchListeners) {
                        slot.directFetchListeners = null;

                        for (var listenerID in directFetchListeners) {
                            directFetchListeners[listenerID].error(error);
                        }

                        releaseSlotIfUnrequested(slot);
                    }
                }

                // Item Requests

                function requestSlot(slot) {
                    // Ensure that there's a slot on either side of each requested item
                    if (slot.firstInSequence) {
                        addSlotBefore(slot, indexMap);
                    }
                    if (slot.lastInSequence) {
                        addSlotAfter(slot, indexMap);
                    }

                    // If the item has already been fetched, prepare it now to be returned to the client
                    if (slot.itemNew) {
                        prepareSlotItem(slot);
                    }

                    // Start a new fetch if necessary
                    postFetch();

                    return slot;
                }

                function requestSlotBefore(slotNext) {
                    // First, see if the previous slot already exists
                    if (!slotNext.firstInSequence) {
                        var slotPrev = slotNext.prev;

                        // Next, see if the item is known to not exist
                        return (slotPrev === slotsStart ? null : requestSlot(slotPrev));
                    }

                    return requestSlot(addSlotBefore(slotNext, indexMap));
                }

                function requestSlotAfter(slotPrev) {
                    // First, see if the next slot already exists
                    if (!slotPrev.lastInSequence) {
                        var slotNext = slotPrev.next;

                        // Next, see if the item is known to not exist
                        return (slotNext === slotListEnd ? null : requestSlot(slotNext));
                    }

                    return requestSlot(addSlotAfter(slotPrev, indexMap));
                }

                function itemDirectlyFromSlot(slot) {
                    // Return a complete promise for a non-existent slot
                    return (
                        slot ?
                            createFetchPromise(slot, "directFetchListeners", (nextListenerID++).toString()) :
                            Promise.wrap(null)
                    );
                }

                function validateKey(key) {
                    if (typeof key !== "string" || !key) {
                        throw new _ErrorFromName("WinJS.UI.ListDataSource.KeyIsInvalid", strings.keyIsInvalid);
                    }
                }

                function createSlotForKey(key) {
                    var slot = createPrimarySlotSequence(slotsEnd);

                    setSlotKey(slot, key);
                    slot.keyRequested = true;

                    return slot;
                }

                function slotFromKey(key, hints) {
                    validateKey(key);

                    var slot = keyMap[key];

                    if (!slot) {
                        slot = createSlotForKey(key);
                        slot.hints = hints;
                    }

                    return requestSlot(slot);
                }

                function slotFromIndex(index) {
                    if (typeof index !== "number" || index < 0) {
                        throw new _ErrorFromName("WinJS.UI.ListDataSource.IndexIsInvalid", strings.indexIsInvalid);
                    }

                    if (slotListEnd.index <= index) {
                        return null;
                    }

                    var slot = indexMap[index];

                    if (!slot) {
                        var slotNext = successorFromIndex(index, indexMap, slotsStart, slotListEnd);

                        if (!slotNext) {
                            // The complete list has been observed, and this index isn't a part of it; a refresh may be necessary
                            return null;
                        }

                        if (slotNext === slotListEnd && index >= slotListEnd) {
                            // Clear slotListEnd's index, as that's now unknown
                            changeSlotIndex(slotListEnd, undefined);
                        }

                        // Create a new slot and start a request for it
                        if (slotNext.prev.index === index - 1) {
                            slot = addSlotAfter(slotNext.prev, indexMap);
                        } else if (slotNext.index === index + 1) {
                            slot = addSlotBefore(slotNext, indexMap);
                        } else {
                            slot = createPrimarySlotSequence(slotNext, index);
                        }
                    }

                    if (!slot.item) {
                        slot.indexRequested = true;
                    }

                    return requestSlot(slot);
                }

                function slotFromDescription(description) {
                    // Create a new slot and start a request for it
                    var slot = createPrimarySlotSequence(slotsEnd);

                    slot.description = description;

                    return requestSlot(slot);
                }

                // Status
                var that = this;
                function setStatus(statusNew) {
                    statusPending = statusNew;
                    if (status !== statusPending) {
                        var dispatch = function () {
                            statusChangePosted = false;

                            if (status !== statusPending) {
                                status = statusPending;
                                that.dispatchEvent(statusChangedEvent, status);
                            }
                        };
                        if (statusPending === DataSourceStatus.failure) {
                            dispatch();
                        } else if (!statusChangePosted) {
                            statusChangePosted = true;

                            // Delay the event to filter out rapid changes
                            _Global.setTimeout(dispatch, 40);
                        }
                    }
                }

                // Slot Fetching

                function slotFetchInProgress(slot) {
                    var fetchID = slot.fetchID;
                    return fetchID && fetchesInProgress[fetchID];
                }

                function setFetchID(slot, fetchID) {
                    slot.fetchID = fetchID;
                }

                function newFetchID() {
                    var fetchID = nextFetchID;
                    nextFetchID++;

                    fetchesInProgress[fetchID] = true;

                    return fetchID;
                }

                function setFetchIDs(slot, countBefore, countAfter) {
                    var fetchID = newFetchID();
                    setFetchID(slot, fetchID);

                    var slotBefore = slot;
                    while (!slotBefore.firstInSequence && countBefore > 0) {
                        slotBefore = slotBefore.prev;
                        countBefore--;
                        setFetchID(slotBefore, fetchID);
                    }

                    var slotAfter = slot;
                    while (!slotAfter.lastInSequence && countAfter > 0) {
                        slotAfter = slotAfter.next;
                        countAfter--;
                        setFetchID(slotAfter, fetchID);
                    }

                    return fetchID;
                }

                // Adds markers on behalf of the data adapter if their presence can be deduced
                function addMarkers(fetchResult) {
                    var items = fetchResult.items,
                        offset = fetchResult.offset,
                        totalCount = fetchResult.totalCount,
                        absoluteIndex = fetchResult.absoluteIndex,
                        atStart = fetchResult.atStart,
                        atEnd = fetchResult.atEnd;

                    if (isNonNegativeNumber(absoluteIndex)) {
                        if (isNonNegativeNumber(totalCount)) {
                            var itemsLength = items.length;
                            if (absoluteIndex - offset + itemsLength === totalCount) {
                                atEnd = true;
                            }
                        }

                        if (offset === absoluteIndex) {
                            atStart = true;
                        }
                    }

                    if (atStart) {
                        items.unshift(startMarker);
                        fetchResult.offset++;
                    }

                    if (atEnd) {
                        items.push(endMarker);
                    }
                }

                function resultsValid(slot, refreshID, fetchID) {
                    // This fetch has completed, whatever it has returned
                    delete fetchesInProgress[fetchID];

                    if (refreshID !== currentRefreshID || slotPermanentlyRemoved(slot)) {
                        // This information is out of date, or the slot has since been discarded

                        postFetch();
                        return false;
                    }

                    return true;
                }

                function fetchItems(slot, fetchID, promiseItems, index) {
                    var refreshID = currentRefreshID;
                    promiseItems.then(function (fetchResult) {
                        if (fetchResult.items && fetchResult.items.length) {
                            var perfID = "itemsFetched id=" + fetchID + " count=" + fetchResult.items.length;
                            profilerMarkStart(perfID);
                            if (resultsValid(slot, refreshID, fetchID)) {
                                if (+index === index) {
                                    fetchResult.absoluteIndex = index;
                                }
                                addMarkers(fetchResult);
                                processResults(slot, fetchResult.items, fetchResult.offset, fetchResult.totalCount, fetchResult.absoluteIndex);
                            }
                            profilerMarkEnd(perfID);
                        } else {
                            return Promise.wrapError(new _ErrorFromName(FetchError.doesNotExist));
                        }
                    }).then(null, function (error) {
                        if (resultsValid(slot, refreshID, fetchID)) {
                            processErrorResult(slot, error);
                        }
                    });
                }

                function fetchItemsForIndex(indexRequested, slot, fetchID, promiseItems) {
                    var refreshID = currentRefreshID;
                    promiseItems.then(function (fetchResult) {
                        if (fetchResult.items && fetchResult.items.length) {
                            var perfID = "itemsFetched id=" + fetchID + " count=" + fetchResult.items.length;
                            profilerMarkStart(perfID);
                            if (resultsValid(slot, refreshID, fetchID)) {
                                fetchResult.absoluteIndex = indexRequested;
                                addMarkers(fetchResult);
                                processResultsForIndex(indexRequested, slot, fetchResult.items, fetchResult.offset, fetchResult.totalCount, fetchResult.absoluteIndex);
                            }
                            profilerMarkEnd(perfID);
                        } else {
                            return Promise.wrapError(new _ErrorFromName(FetchError.doesNotExist));
                        }
                    }).then(null, function () {
                        if (resultsValid(slot, refreshID, fetchID)) {
                            processErrorResultForIndex(indexRequested, slot, refreshID);
                        }
                    });
                }

                function fetchItemsFromStart(slot, count) {
                    var fetchID = setFetchIDs(slot, 0, count - 1);
                    if (itemsFromStart) {
                        fetchItems(slot, fetchID, itemsFromStart(fetchID, count), 0);
                    } else {
                        fetchItems(slot, fetchID, itemsFromIndex(fetchID, 0, 0, count - 1), 0);
                    }
                }

                function fetchItemsFromEnd(slot, count) {
                    var fetchID = setFetchIDs(slot, count - 1, 0);
                    fetchItems(slot, fetchID, itemsFromEnd(fetchID, count));
                }

                function fetchItemsFromKey(slot, countBefore, countAfter) {
                    var fetchID = setFetchIDs(slot, countBefore, countAfter);
                    fetchItems(slot, fetchID, itemsFromKey(fetchID, slot.key, countBefore, countAfter, slot.hints));
                }

                function fetchItemsFromIndex(slot, countBefore, countAfter) {
                    var index = slot.index;

                    // Don't ask for items with negative indices
                    if (countBefore > index) {
                        countBefore = index;
                    }

                    if (itemsFromIndex) {
                        var fetchID = setFetchIDs(slot, countBefore, countAfter);
                        fetchItems(slot, fetchID, itemsFromIndex(fetchID, index, countBefore, countAfter), index);
                    } else {
                        // If the slot key is known, we just need to request the surrounding items
                        if (slot.key) {
                            fetchItemsFromKey(slot, countBefore, countAfter);
                        } else {
                            // Search for the slot with the closest index that has a known key (using the start of the list as a
                            // last resort).
                            var slotClosest = slotsStart,
                                closestDelta = index + 1,
                                slotSearch,
                                delta;

                            // First search backwards
                            for (slotSearch = slot.prev; slotSearch !== slotsStart; slotSearch = slotSearch.prev) {
                                if (slotSearch.index !== undefined && slotSearch.key) {
                                    delta = index - slotSearch.index;
                                    if (closestDelta > delta) {
                                        closestDelta = delta;
                                        slotClosest = slotSearch;
                                    }
                                    break;
                                }
                            }

                            // Then search forwards
                            for (slotSearch = slot.next; slotSearch !== slotListEnd; slotSearch = slotSearch.next) {
                                if (slotSearch.index !== undefined && slotSearch.key) {
                                    delta = slotSearch.index - index;
                                    if (closestDelta > delta) {
                                        closestDelta = delta;
                                        slotClosest = slotSearch;
                                    }
                                    break;
                                }
                            }

                            if (slotClosest === slotsStart) {
                                var fetchID = setFetchIDs(slot, 0, index + 1);
                                fetchItemsForIndex(0, slot, fetchID, itemsFromStart(fetchID, index + 1));
                            } else {
                                var fetchBefore = Math.max(slotClosest.index - index, 0);
                                var fetchAfter = Math.max(index - slotClosest.index, 0);
                                var fetchID = setFetchIDs(slotClosest, fetchBefore, fetchAfter);
                                fetchItemsForIndex(slotClosest.index, slot, fetchID, itemsFromKey(fetchID,
                                    slotClosest.key,
                                    fetchBefore,
                                    fetchAfter,
                                    slot.hints
                                ));
                            }
                        }
                    }
                }

                function fetchItemsFromDescription(slot, countBefore, countAfter) {
                    var fetchID = setFetchIDs(slot, countBefore, countAfter);
                    fetchItems(slot, fetchID, itemsFromDescription(fetchID, slot.description, countBefore, countAfter));
                }

                function fetchItemsForAllSlots() {
                    if (!refreshInProgress) {
                        var slotFirstPlaceholder,
                            placeholderCount,
                            fetchInProgress = false,
                            fetchesInProgress = false,
                            slotRequestedByKey,
                            requestedKeyOffset,
                            slotRequestedByDescription,
                            requestedDescriptionOffset,
                            slotRequestedByIndex,
                            requestedIndexOffset;

                        for (var slot = slotsStart.next; slot !== slotsEnd;) {
                            var slotNext = slot.next;

                            if (slot !== slotListEnd && isPlaceholder(slot)) {
                                fetchesInProgress = true;

                                if (!slotFirstPlaceholder) {
                                    slotFirstPlaceholder = slot;
                                    placeholderCount = 1;
                                } else {
                                    placeholderCount++;
                                }

                                if (slotFetchInProgress(slot)) {
                                    fetchInProgress = true;
                                }

                                if (slot.keyRequested && !slotRequestedByKey) {
                                    slotRequestedByKey = slot;
                                    requestedKeyOffset = placeholderCount - 1;
                                }

                                if (slot.description !== undefined && !slotRequestedByDescription) {
                                    slotRequestedByDescription = slot;
                                    requestedDescriptionOffset = placeholderCount - 1;
                                }

                                if (slot.indexRequested && !slotRequestedByIndex) {
                                    slotRequestedByIndex = slot;
                                    requestedIndexOffset = placeholderCount - 1;
                                }

                                if (slot.lastInSequence || slotNext === slotsEnd || !isPlaceholder(slotNext)) {
                                    if (fetchInProgress) {
                                        fetchInProgress = false;
                                    } else {
                                        resultsProcessed = false;

                                        // Start a new fetch for this placeholder sequence

                                        // Prefer fetches in terms of a known item
                                        if (!slotFirstPlaceholder.firstInSequence && slotFirstPlaceholder.prev.key && itemsFromKey) {
                                            fetchItemsFromKey(slotFirstPlaceholder.prev, 0, placeholderCount);
                                        } else if (!slot.lastInSequence && slotNext.key && itemsFromKey) {
                                            fetchItemsFromKey(slotNext, placeholderCount, 0);
                                        } else if (slotFirstPlaceholder.prev === slotsStart && !slotFirstPlaceholder.firstInSequence && (itemsFromStart || itemsFromIndex)) {
                                            fetchItemsFromStart(slotFirstPlaceholder, placeholderCount);
                                        } else if (slotNext === slotListEnd && !slot.lastInSequence && itemsFromEnd) {
                                            fetchItemsFromEnd(slot, placeholderCount);
                                        } else if (slotRequestedByKey) {
                                            fetchItemsFromKey(slotRequestedByKey, requestedKeyOffset, placeholderCount - 1 - requestedKeyOffset);
                                        } else if (slotRequestedByDescription) {
                                            fetchItemsFromDescription(slotRequestedByDescription, requestedDescriptionOffset, placeholderCount - 1 - requestedDescriptionOffset);
                                        } else if (slotRequestedByIndex) {
                                            fetchItemsFromIndex(slotRequestedByIndex, requestedIndexOffset, placeholderCount - 1 - requestedIndexOffset);
                                        } else if (typeof slotFirstPlaceholder.index === "number") {
                                            fetchItemsFromIndex(slotFirstPlaceholder, placeholderCount - 1, 0);
                                        } else {
                                            // There is no way to fetch anything in this sequence
                                            deleteMirageSequence(slotFirstPlaceholder);
                                        }

                                        if (resultsProcessed) {
                                            // A re-entrant fetch might have altered the slots list - start again
                                            postFetch();
                                            return;
                                        }

                                        if (refreshInProgress) {
                                            // A re-entrant fetch might also have caused a refresh
                                            return;
                                        }
                                    }

                                    slotFirstPlaceholder = slotRequestedByIndex = slotRequestedByKey = null;
                                }
                            }

                            slot = slotNext;
                        }

                        setStatus(fetchesInProgress ? DataSourceStatus.waiting : DataSourceStatus.ready);
                    }
                }

                function postFetch() {
                    if (!fetchesPosted) {
                        fetchesPosted = true;
                        Scheduler.schedule(function VDS_async_postFetch() {
                            fetchesPosted = false;
                            fetchItemsForAllSlots();

                            // A mirage sequence might have been removed
                            finishNotifications();
                        }, Scheduler.Priority.max, null, "WinJS.UI.ListDataSource._fetch");
                    }
                }

                // Fetch Result Processing

                function itemChanged(slot) {
                    var itemNew = slot.itemNew;

                    if (!itemNew) {
                        return false;
                    }

                    var item = slot.item;

                    for (var property in item) {
                        switch (property) {
                            case "data":
                                // This is handled below
                                break;

                            default:
                                if (item[property] !== itemNew[property]) {
                                    return true;
                                }
                                break;
                        }
                    }

                    return (
                        listDataAdapter.compareByIdentity ?
                            item.data !== itemNew.data :
                            slot.signature !== itemSignature(itemNew)
                    );
                }

                function changeSlotIfNecessary(slot) {
                    if (!slotRequested(slot)) {
                        // There's no need for any notifications, just delete the old item
                        slot.item = null;
                    } else if (itemChanged(slot)) {
                        changeSlot(slot);
                    } else {
                        slot.itemNew = null;
                    }
                }

                function updateSlotItem(slot) {
                    if (slot.item) {
                        changeSlotIfNecessary(slot);
                    } else {
                        completeFetchPromises(slot);
                    }
                }

                function updateSlot(slot, item) {
                    if (!slot.key) {
                        setSlotKey(slot, item.key);
                    }
                    slot.itemNew = item;

                    updateSlotItem(slot);
                }

                function sendMirageNotifications(slot, slotToDiscard, listBindingIDsToDelete) {
                    var bindingMap = slotToDiscard.bindingMap;
                    if (bindingMap) {
                        for (var listBindingID in listBindingIDsToDelete) {
                            if (bindingMap[listBindingID]) {
                                var fetchListeners = slotToDiscard.fetchListeners;
                                for (var listenerID in fetchListeners) {
                                    var listener = fetchListeners[listenerID];

                                    if (listener.listBindingID === listBindingID && listener.retained) {
                                        delete fetchListeners[listenerID];
                                        listener.complete(null);
                                    }
                                }

                                var bindingRecord = bindingMap[listBindingID].bindingRecord;
                                handlerToNotify(bindingRecord).removed(handleForBinding(slotToDiscard, listBindingID), true, handleForBinding(slot, listBindingID));

                                // A re-entrant call to release from the removed handler might have cleared slotToDiscard.bindingMap
                                if (slotToDiscard.bindingMap) {
                                    delete slotToDiscard.bindingMap[listBindingID];
                                }
                            }
                        }
                    }
                }

                function mergeSlots(slot, slotToDiscard) {
                    // This shouldn't be called on a slot that has a pending change notification
                    // Only one of the two slots should have a key
                    // If slotToDiscard is about to acquire an index, send the notifications now; in rare cases, multiple
                    // indexChanged notifications will be sent for a given item during a refresh, but that's fine.
                    if (slot.index !== slotToDiscard.index) {
                        // If slotToDiscard has a defined index, that should have been transferred already
                        var indexOld = slotToDiscard.index;
                        slotToDiscard.index = slot.index;
                        sendIndexChangedNotifications(slotToDiscard, indexOld);
                    }

                    slotToDiscard.slotMergedWith = slot;

                    // Transfer the slotBindings from slotToDiscard to slot
                    var bindingMap = slotToDiscard.bindingMap;
                    for (var listBindingID in bindingMap) {
                        if (!slot.bindingMap) {
                            slot.bindingMap = {};
                        }

                        var slotBinding = bindingMap[listBindingID];

                        if (!slotBinding.handle) {
                            slotBinding.handle = slotToDiscard.handle;
                        }
                        handleMap[slotBinding.handle] = slot;

                        slot.bindingMap[listBindingID] = slotBinding;
                    }

                    // Update any ListBinding cursors pointing to slotToDiscard
                    forEachBindingRecord(function (bindingRecord) {
                        bindingRecord.adjustCurrentSlot(slotToDiscard, slot);
                    });

                    // See if the item needs to be transferred from slotToDiscard to slot
                    var item = slotToDiscard.itemNew || slotToDiscard.item;
                    if (item) {
                        item = Object.create(item);
                        defineCommonItemProperties(item, slot, slot.handle);
                        updateSlot(slot, item);
                    }

                    // Transfer the fetch listeners from slotToDiscard to slot, or complete them if item is known
                    if (slot.item) {
                        if (slotToDiscard.directFetchListeners) {
                            fetchCompleteCallbacks.push(function () {
                                completePromises(slot.item, slotToDiscard.directFetchListeners);
                            });
                        }
                        if (slotToDiscard.fetchListeners) {
                            fetchCompleteCallbacks.push(function () {
                                completePromises(slot.item, slotToDiscard.fetchListeners);
                            });
                        }
                    } else {
                        var listenerID;

                        for (listenerID in slotToDiscard.directFetchListeners) {
                            if (!slot.directFetchListeners) {
                                slot.directFetchListeners = {};
                            }
                            slot.directFetchListeners[listenerID] = slotToDiscard.directFetchListeners[listenerID];
                        }

                        for (listenerID in slotToDiscard.fetchListeners) {
                            if (!slot.fetchListeners) {
                                slot.fetchListeners = {};
                            }
                            slot.fetchListeners[listenerID] = slotToDiscard.fetchListeners[listenerID];
                        }
                    }

                    // This might be the first time this slot's item can be prepared
                    if (slot.itemNew) {
                        completeFetchPromises(slot);
                    }

                    // Give slotToDiscard an unused handle so it appears to be permanently removed
                    slotToDiscard.handle = (nextHandle++).toString();

                    splitSequence(slotToDiscard);
                    removeSlotPermanently(slotToDiscard);
                }

                function mergeSlotsAndItem(slot, slotToDiscard, item) {
                    if (slotToDiscard && slotToDiscard.key) {
                        if (!item) {
                            item = slotToDiscard.itemNew || slotToDiscard.item;
                        }

                        // Free up the key for the promoted slot
                        delete slotToDiscard.key;
                        delete keyMap[item.key];

                        slotToDiscard.itemNew = null;
                        slotToDiscard.item = null;
                    }

                    if (item) {
                        updateSlot(slot, item);
                    }

                    if (slotToDiscard) {
                        mergeSlots(slot, slotToDiscard);
                    }
                }

                function slotFromResult(result) {
                    if (typeof result !== "object") {
                        throw new _ErrorFromName("WinJS.UI.ListDataSource.InvalidItemReturned", strings.invalidItemReturned);
                    } else if (result === startMarker) {
                        return slotsStart;
                    } else if (result === endMarker) {
                        return slotListEnd;
                    } else if (!result.key) {
                        throw new _ErrorFromName("WinJS.UI.ListDataSource.InvalidKeyReturned", strings.invalidKeyReturned);
                    } else {
                        if (_BaseUtils.validation) {
                            validateKey(result.key);
                        }
                        return keyMap[result.key];
                    }
                }

                function matchSlot(slot, result) {
                    // First see if there is an existing slot that needs to be merged
                    var slotExisting = slotFromResult(result);
                    if (slotExisting === slot) {
                        slotExisting = null;
                    }

                    if (slotExisting) {
                        sendMirageNotifications(slot, slotExisting, slot.bindingMap);
                    }

                    mergeSlotsAndItem(slot, slotExisting, result);
                }

                function promoteSlot(slot, item, index, insertionPoint) {
                    if (item && slot.key && slot.key !== item.key) {
                        // A contradiction has been found
                        beginRefresh();
                        return false;
                    }

                    // The slot with the key "wins"; slots without bindings can be merged without any change in observable behavior

                    var slotWithIndex = indexMap[index];
                    if (slotWithIndex) {
                        if (slotWithIndex === slot) {
                            slotWithIndex = null;
                        } else if (slotWithIndex.key && (slot.key || (item && slotWithIndex.key !== item.key))) {
                            // A contradiction has been found
                            beginRefresh();
                            return false;
                        } else if (!slot.key && slotWithIndex.bindingMap) {
                            return false;
                        }
                    }

                    var slotWithKey;
                    if (item) {
                        slotWithKey = keyMap[item.key];

                        if (slotWithKey === slot) {
                            slotWithKey = null;
                        } else if (slotWithKey && slotWithKey.bindingMap) {
                            return false;
                        }
                    }

                    if (slotWithIndex) {
                        sendMirageNotifications(slot, slotWithIndex, slot.bindingMap);

                        // Transfer the index to the promoted slot
                        delete indexMap[index];
                        changeSlotIndex(slot, index);

                        // See if this sequence should be merged with its neighbors
                        if (slot.prev.index === index - 1) {
                            mergeSequences(slot.prev);
                        }
                        if (slot.next.index === index + 1) {
                            mergeSequences(slot);
                        }

                        insertionPoint.slotNext = slotWithIndex.slotNext;

                        if (!item) {
                            item = slotWithIndex.itemNew || slotWithIndex.item;
                            if (item) {
                                slotWithKey = keyMap[item.key];
                            }
                        }
                    } else {
                        changeSlotIndex(slot, index);
                    }

                    if (slotWithKey && slotWithIndex !== slotWithKey) {
                        sendMirageNotifications(slot, slotWithKey, slot.bindingMap);
                    }

                    mergeSlotsAndItem(slot, slotWithKey, item);

                    // Do this after mergeSlotsAndItem, since its call to updateSlot might send changed notifications, and those
                    // wouldn't make sense to clients that never saw the old item.
                    if (slotWithIndex && slotWithIndex !== slotWithKey) {
                        mergeSlots(slot, slotWithIndex);
                    }

                    return true;
                }

                function mergeAdjacentSlot(slotExisting, slot, listBindingIDsToDelete) {
                    if (slot.key && slotExisting.key && slot.key !== slotExisting.key) {
                        // A contradiction has been found
                        beginRefresh();
                        return false;
                    }

                    for (var listBindingID in slotExisting.bindingMap) {
                        listBindingIDsToDelete[listBindingID] = true;
                    }

                    sendMirageNotifications(slotExisting, slot, listBindingIDsToDelete);
                    mergeSlotsAndItem(slotExisting, slot);

                    return true;
                }

                function mergeSlotsBefore(slot, slotExisting) {
                    var listBindingIDsToDelete = {};

                    while (slot) {
                        var slotPrev = (slot.firstInSequence ? null : slot.prev);

                        if (!slotExisting.firstInSequence && slotExisting.prev === slotsStart) {
                            deleteSlot(slot, true);
                        } else {
                            if (slotExisting.firstInSequence) {
                                slotExisting = addSlotBefore(slotExisting, indexMap);
                            } else {
                                slotExisting = slotExisting.prev;
                            }

                            if (!mergeAdjacentSlot(slotExisting, slot, listBindingIDsToDelete)) {
                                return;
                            }
                        }

                        slot = slotPrev;
                    }
                }

                function mergeSlotsAfter(slot, slotExisting) {
                    var listBindingIDsToDelete = {};

                    while (slot) {
                        var slotNext = (slot.lastInSequence ? null : slot.next);

                        if (!slotExisting.lastInSequence && slotExisting.next === slotListEnd) {
                            deleteSlot(slot, true);
                        } else {
                            if (slotExisting.lastInSequence) {
                                slotExisting = addSlotAfter(slotExisting, indexMap);
                            } else {
                                slotExisting = slotExisting.next;
                            }

                            if (!mergeAdjacentSlot(slotExisting, slot, listBindingIDsToDelete)) {
                                return;
                            }
                        }

                        slot = slotNext;
                    }
                }

                function mergeSequencePairs(sequencePairsToMerge) {
                    for (var i = 0; i < sequencePairsToMerge.length; i++) {
                        var sequencePairToMerge = sequencePairsToMerge[i];
                        mergeSlotsBefore(sequencePairToMerge.slotBeforeSequence, sequencePairToMerge.slotFirstInSequence);
                        mergeSlotsAfter(sequencePairToMerge.slotAfterSequence, sequencePairToMerge.slotLastInSequence);
                    }
                }

                // Removes any placeholders with indices that exceed the given upper bound on the count
                function removeMirageIndices(countMax, indexFirstKnown) {
                    var placeholdersAtEnd = 0;

                    function removePlaceholdersAfterSlot(slotRemoveAfter) {
                        for (var slot2 = slotListEnd.prev; !(slot2.index < countMax) && slot2 !== slotRemoveAfter;) {
                            var slotPrev2 = slot2.prev;
                            if (slot2.index !== undefined) {
                                deleteSlot(slot2, true);
                            }
                            slot2 = slotPrev2;
                        }

                        placeholdersAtEnd = 0;
                    }

                    for (var slot = slotListEnd.prev; !(slot.index < countMax) || placeholdersAtEnd > 0;) {
                        var slotPrev = slot.prev;

                        if (slot === slotsStart) {
                            removePlaceholdersAfterSlot(slotsStart);
                            break;
                        } else if (slot.key) {
                            if (slot.index >= countMax) {
                                beginRefresh();
                                return false;
                            } else if (slot.index >= indexFirstKnown) {
                                removePlaceholdersAfterSlot(slot);
                            } else {
                                if (itemsFromKey) {
                                    fetchItemsFromKey(slot, 0, placeholdersAtEnd);
                                } else {
                                    fetchItemsFromIndex(slot, 0, placeholdersAtEnd);
                                }

                                // Wait until the fetch has completed before doing anything
                                return false;
                            }
                        } else if (slot.indexRequested || slot.firstInSequence) {
                            removePlaceholdersAfterSlot(slotPrev);
                        } else {
                            placeholdersAtEnd++;
                        }

                        slot = slotPrev;
                    }

                    return true;
                }

                // Merges the results of a fetch into the slot list data structure, and determines if any notifications need to be
                // synthesized.
                function processResults(slot, results, offset, count, index) {
                    var perfId = "WinJS.UI.ListDataSource.processResults";
                    profilerMarkStart(perfId);

                    index = validateIndexReturned(index);
                    count = validateCountReturned(count);

                    // If there are edits queued, we need to wait until the slots get back in sync with the data
                    if (editsQueued) {
                        profilerMarkEnd(perfId);
                        return;
                    }

                    if (indexUpdateDeferred) {
                        updateIndices();
                    }

                    // If the count has changed, and the end of the list has been reached, that's a contradiction
                    if ((isNonNegativeNumber(count) || count === CountResult.unknown) && count !== knownCount && !slotListEnd.firstInSequence) {
                        beginRefresh();
                        profilerMarkEnd(perfId);
                        return;
                    }

                    resultsProcessed = true;

                    (function () {
                        var i,
                            j,
                            resultsCount = results.length,
                            slotExisting,
                            slotBefore;

                        // If an index wasn't passed in, see if the indices of these items can be determined
                        if (typeof index !== "number") {
                            for (i = 0; i < resultsCount; i++) {
                                slotExisting = slotFromResult(results[i]);
                                if (slotExisting && slotExisting.index !== undefined) {
                                    index = slotExisting.index + offset - i;
                                    break;
                                }
                            }
                        }

                        // See if these results include the end of the list
                        if (typeof index === "number" && results[resultsCount - 1] === endMarker) {
                            // If the count wasn't known, it is now
                            count = index - offset + resultsCount - 1;
                        } else if (isNonNegativeNumber(count) && (index === undefined || index === null)) {
                            // If the index wasn't known, it is now
                            index = count - (resultsCount - 1) + offset;
                        }

                        // If the count is known, remove any mirage placeholders at the end
                        if (isNonNegativeNumber(count) && !removeMirageIndices(count, index - offset)) {
                            // "Forget" the count - a subsequent fetch or refresh will update the count and list end
                            count = undefined;
                        }

                        // Find any existing slots that correspond to the results, and check for contradictions
                        var offsetMap = new Array(resultsCount);
                        for (i = 0; i < resultsCount; i++) {
                            var slotBestMatch = null;

                            slotExisting = slotFromResult(results[i]);

                            if (slotExisting) {
                                // See if this item is currently adjacent to a different item, or has a different index
                                if ((i > 0 && !slotExisting.firstInSequence && slotExisting.prev.key && slotExisting.prev.key !== results[i - 1].key) ||
                                        (typeof index === "number" && slotExisting.index !== undefined && slotExisting.index !== index - offset + i)) {
                                    // A contradiction has been found, so we can't proceed further
                                    beginRefresh();
                                    return;
                                }

                                if (slotExisting === slotsStart || slotExisting === slotListEnd || slotExisting.bindingMap) {
                                    // First choice is a slot with the given key and at least one binding (or an end of the list)
                                    slotBestMatch = slotExisting;
                                }
                            }

                            if (typeof index === "number") {
                                slotExisting = indexMap[index - offset + i];

                                if (slotExisting) {
                                    if (slotExisting.key && slotExisting.key !== results[i].key) {
                                        // A contradiction has been found, so we can't proceed further
                                        beginRefresh();
                                        return;
                                    }

                                    if (!slotBestMatch && slotExisting.bindingMap) {
                                        // Second choice is a slot with the given index and at least one binding
                                        slotBestMatch = slotExisting;
                                    }
                                }
                            }

                            if (i === offset) {
                                if ((slot.key && slot.key !== results[i].key) || (typeof slot.index === "number" && typeof index === "number" && slot.index !== index)) {
                                    // A contradiction has been found, so we can't proceed further
                                    beginRefresh();
                                    return;
                                }

                                if (!slotBestMatch) {
                                    // Third choice is the slot that was passed in
                                    slotBestMatch = slot;
                                }
                            }

                            offsetMap[i] = slotBestMatch;
                        }

                        // Update items with known indices (and at least one binding) first, as they will not be merged with
                        // anything.
                        for (i = 0; i < resultsCount; i++) {
                            slotExisting = offsetMap[i];
                            if (slotExisting && slotExisting.index !== undefined && slotExisting !== slotsStart && slotExisting !== slotListEnd) {
                                matchSlot(slotExisting, results[i]);
                            }
                        }

                        var sequencePairsToMerge = [];

                        // Now process the sequences without indices
                        var firstSequence = true;
                        var slotBeforeSequence;
                        var slotAfterSequence;
                        for (i = 0; i < resultsCount; i++) {
                            slotExisting = offsetMap[i];
                            if (slotExisting && slotExisting !== slotListEnd) {
                                var iLast = i;

                                if (slotExisting.index === undefined) {
                                    var insertionPoint = {};

                                    promoteSlot(slotExisting, results[i], index - offset + i, insertionPoint);

                                    // Find the extents of the sequence of slots that we can use
                                    var slotFirstInSequence = slotExisting,
                                        slotLastInSequence = slotExisting,
                                        result;

                                    for (j = i - 1; !slotFirstInSequence.firstInSequence; j--) {
                                        // Keep going until we hit the start marker or a slot that we can't use or promote (it's ok
                                        // if j leaves the results range).

                                        result = results[j];
                                        if (result === startMarker) {
                                            break;
                                        }

                                        // Avoid assigning negative indices to slots
                                        var index2 = index - offset + j;
                                        if (index2 < 0) {
                                            break;
                                        }

                                        if (promoteSlot(slotFirstInSequence.prev, result, index2, insertionPoint)) {
                                            slotFirstInSequence = slotFirstInSequence.prev;
                                            if (j >= 0) {
                                                offsetMap[j] = slotFirstInSequence;
                                            }
                                        } else {
                                            break;
                                        }
                                    }

                                    for (j = i + 1; !slotLastInSequence.lastInSequence; j++) {
                                        // Keep going until we hit the end marker or a slot that we can't use or promote (it's ok
                                        // if j leaves the results range).

                                        // If slotListEnd is in this sequence, it should not be separated from any predecessor
                                        // slots, but they may need to be promoted.
                                        result = results[j];
                                        if ((result === endMarker || j === count) && slotLastInSequence.next !== slotListEnd) {
                                            break;
                                        }

                                        if (slotLastInSequence.next === slotListEnd || promoteSlot(slotLastInSequence.next, result, index - offset + j, insertionPoint)) {
                                            slotLastInSequence = slotLastInSequence.next;
                                            if (j < resultsCount) {
                                                offsetMap[j] = slotLastInSequence;
                                            }

                                            iLast = j;

                                            if (slotLastInSequence === slotListEnd) {
                                                break;
                                            }
                                        } else {
                                            break;
                                        }
                                    }

                                    slotBeforeSequence = (slotFirstInSequence.firstInSequence ? null : slotFirstInSequence.prev);
                                    slotAfterSequence = (slotLastInSequence.lastInSequence ? null : slotLastInSequence.next);

                                    if (slotBeforeSequence) {
                                        splitSequence(slotBeforeSequence);
                                    }
                                    if (slotAfterSequence) {
                                        splitSequence(slotLastInSequence);
                                    }

                                    // Move the sequence if necessary
                                    if (typeof index === "number") {
                                        if (slotLastInSequence === slotListEnd) {
                                            // Instead of moving the list end, move the sequence before out of the way
                                            if (slotBeforeSequence) {
                                                moveSequenceAfter(slotListEnd, sequenceStart(slotBeforeSequence), slotBeforeSequence);
                                            }
                                        } else {
                                            var slotInsertBefore = insertionPoint.slotNext;
                                            if (!slotInsertBefore) {
                                                slotInsertBefore = successorFromIndex(slotLastInSequence.index, indexMap, slotsStart, slotListEnd, true);
                                            }
                                            moveSequenceBefore(slotInsertBefore, slotFirstInSequence, slotLastInSequence);
                                        }
                                        if (slotFirstInSequence.prev.index === slotFirstInSequence.index - 1) {
                                            mergeSequences(slotFirstInSequence.prev);
                                        }
                                        if (slotLastInSequence.next.index === slotLastInSequence.index + 1) {
                                            mergeSequences(slotLastInSequence);
                                        }
                                    } else if (!firstSequence) {
                                        slotBefore = offsetMap[i - 1];

                                        if (slotBefore) {
                                            if (slotFirstInSequence.prev !== slotBefore) {
                                                if (slotLastInSequence === slotListEnd) {
                                                    // Instead of moving the list end, move the sequence before out of the way and
                                                    // the predecessor sequence into place.
                                                    if (slotBeforeSequence) {
                                                        moveSequenceAfter(slotListEnd, sequenceStart(slotBeforeSequence), slotBeforeSequence);
                                                    }
                                                    moveSequenceBefore(slotFirstInSequence, sequenceStart(slotBefore), slotBefore);
                                                } else {
                                                    moveSequenceAfter(slotBefore, slotFirstInSequence, slotLastInSequence);
                                                }
                                            }
                                            mergeSequences(slotBefore);
                                        }
                                    }
                                    firstSequence = false;

                                    if (refreshRequested) {
                                        return;
                                    }

                                    sequencePairsToMerge.push({
                                        slotBeforeSequence: slotBeforeSequence,
                                        slotFirstInSequence: slotFirstInSequence,
                                        slotLastInSequence: slotLastInSequence,
                                        slotAfterSequence: slotAfterSequence
                                    });
                                }

                                // See if the fetched slot needs to be merged
                                if (i === offset && slotExisting !== slot && !slotPermanentlyRemoved(slot)) {
                                    slotBeforeSequence = (slot.firstInSequence ? null : slot.prev);
                                    slotAfterSequence = (slot.lastInSequence ? null : slot.next);

                                    sendMirageNotifications(slotExisting, slot, slotExisting.bindingMap);
                                    mergeSlots(slotExisting, slot);

                                    sequencePairsToMerge.push({
                                        slotBeforeSequence: slotBeforeSequence,
                                        slotFirstInSequence: slotExisting,
                                        slotLastInSequence: slotExisting,
                                        slotAfterSequence: slotAfterSequence
                                    });
                                }

                                // Skip past all the other items in the sequence we just processed
                                i = iLast;
                            }
                        }

                        // If the count is known, set the index of the list end (wait until now because promoteSlot can sometimes
                        // delete it; do this before mergeSequencePairs so the list end can have slots inserted immediately before
                        // it).
                        if (isNonNegativeNumber(count) && slotListEnd.index !== count) {
                            changeSlotIndex(slotListEnd, count);
                        }

                        // Now that all the sequences have been moved, merge any colliding slots
                        mergeSequencePairs(sequencePairsToMerge);

                        // Match or cache any leftover items
                        for (i = 0; i < resultsCount; i++) {
                            // Find the first matched item
                            slotExisting = offsetMap[i];
                            if (slotExisting) {
                                for (j = i - 1; j >= 0; j--) {
                                    var slotAfter = offsetMap[j + 1];
                                    matchSlot(offsetMap[j] = (slotAfter.firstInSequence ? addSlotBefore(offsetMap[j + 1], indexMap) : slotAfter.prev), results[j]);
                                }
                                for (j = i + 1; j < resultsCount; j++) {
                                    slotBefore = offsetMap[j - 1];
                                    slotExisting = offsetMap[j];
                                    if (!slotExisting) {
                                        matchSlot(offsetMap[j] = (slotBefore.lastInSequence ? addSlotAfter(slotBefore, indexMap) : slotBefore.next), results[j]);
                                    } else if (slotExisting.firstInSequence) {
                                        // Adding the cached items may result in some sequences merging
                                        if (slotExisting.prev !== slotBefore) {
                                            moveSequenceAfter(slotBefore, slotExisting, sequenceEnd(slotExisting));
                                        }
                                        mergeSequences(slotBefore);
                                    }
                                }
                                break;
                            }
                        }

                        // The description is no longer required
                        delete slot.description;
                    })();

                    if (!refreshRequested) {
                        // If the count changed, but that's the only thing, just send the notification
                        if (count !== undefined && count !== knownCount) {
                            changeCount(count);
                        }

                        // See if there are more requests we can now fulfill
                        postFetch();
                    }

                    finishNotifications();

                    // Finally complete any promises for newly obtained items
                    callFetchCompleteCallbacks();
                    profilerMarkEnd(perfId);
                }

                function processErrorResult(slot, error) {
                    switch (error.name) {
                        case FetchError.noResponse:
                            setStatus(DataSourceStatus.failure);
                            returnDirectFetchError(slot, error);
                            break;

                        case FetchError.doesNotExist:
                            // Don't return an error, just complete with null (when the slot is deleted)

                            if (slot.indexRequested) {
                                // We now have an upper bound on the count
                                removeMirageIndices(slot.index);
                            } else if (slot.keyRequested || slot.description) {
                                // This item, and any items in the same sequence, count as mirages, since they might never have
                                // existed.
                                deleteMirageSequence(slot);
                            }

                            finishNotifications();

                            // It's likely that the client requested this item because something has changed since the client's
                            // latest observations of the data.  Begin a refresh just in case.
                            beginRefresh();
                            break;
                    }
                }

                function processResultsForIndex(indexRequested, slot, results, offset, count, index) {
                    index = validateIndexReturned(index);
                    count = validateCountReturned(count);

                    var indexFirst = indexRequested - offset;

                    var resultsCount = results.length;
                    if (slot.index >= indexFirst && slot.index < indexFirst + resultsCount) {
                        // The item is in this batch of results - process them all
                        processResults(slot, results, slot.index - indexFirst, count, slot.index);
                    } else if ((offset === resultsCount - 1 && indexRequested < slot.index) || (isNonNegativeNumber(count) && count <= slot.index)) {
                        // The requested index does not exist
                        processErrorResult(slot, new _ErrorFromName(FetchError.doesNotExist));
                    } else {
                        // We didn't get all the results we requested - pick up where they left off
                        if (slot.index < indexFirst) {
                            var fetchID = setFetchIDs(slot, 0, indexFirst - slot.index);
                            fetchItemsForIndex(indexFirst, slot, fetchID, itemsFromKey(
                                fetchID,
                                results[0].key,
                                indexFirst - slot.index,
                                0
                            ));
                        } else {
                            var indexLast = indexFirst + resultsCount - 1;
                            var fetchID = setFetchIDs(slot, slot.index - indexLast, 0);
                            fetchItemsForIndex(indexLast, slot, fetchID, itemsFromKey(
                                fetchID,
                                results[resultsCount - 1].key,
                                0,
                                slot.index - indexLast
                            ));
                        }
                    }
                }

                function processErrorResultForIndex(indexRequested, slot, error) {
                    // If the request was for an index other than the initial one, and the result was doesNotExist, this doesn't
                    switch (error.name) {
                        case FetchError.doesNotExist:
                            if (indexRequested === slotsStart.index) {
                                // The request was for the start of the list, so the item must not exist, and we now have an upper
                                // bound of zero for the count.
                                removeMirageIndices(0);

                                processErrorResult(slot, error);

                                // No need to check return value of removeMirageIndices, since processErrorResult is going to start
                                // a refresh anyway.
                            } else {
                                // Something has changed, but this index might still exist, so request a refresh
                                beginRefresh();
                            }
                            break;

                        default:
                            processErrorResult(slot, error);
                            break;
                    }
                }

                // Refresh

                function identifyRefreshCycle() {
                    // find refresh cycles, find the first beginRefresh in the refreshHistory and see whether it
                    // matches the next beginRefresh, if so then move the data source into an error state and stop
                    // refreshing.
                    var start = 0;
                    for (; start < refreshHistory.length; start++) {
                        if (refreshHistory[start].kind === "beginRefresh") {
                            break;
                        }
                    }
                    var end = start;
                    for (; end < refreshHistory.length; end++) {
                        if (refreshHistory[end].kind === "beginRefresh") {
                            break;
                        }
                    }
                    if (end > start && (end + (end - start) < refreshHistory.length)) {
                        var match = true;
                        var length = end - start;
                        for (var i = 0; i < length; i++) {
                            if (refreshHistory[start + i].kind !== refreshHistory[end + i].kind) {
                                match = false;
                                break;
                            }
                        }
                        if (match) {
                            if (_Log.log) {
                                _Log.log(strings.refreshCycleIdentified, "winjs vds", "error");
                                for (var i = start; i < end; i++) {
                                    _Log.log("" + (i - start) + ": " + JSON.stringify(refreshHistory[i]), "winjs vds", "error");
                                }
                            }
                        }
                        return match;
                    }
                }

                function resetRefreshState() {
                    if (++beginRefreshCount > MAX_BEGINREFRESH_COUNT) {
                        if (identifyRefreshCycle()) {
                            setStatus(DataSourceStatus.failure);
                            return;
                        }
                    }
                    refreshHistory[++refreshHistoryPos % refreshHistory.length] = { kind: "beginRefresh" };

                    // Give the start sentinel an index so we can always use predecessor + 1
                    refreshStart = {
                        firstInSequence: true,
                        lastInSequence: true,
                        index: -1
                    };
                    refreshEnd = {
                        firstInSequence: true,
                        lastInSequence: true
                    };
                    refreshStart.next = refreshEnd;
                    refreshEnd.prev = refreshStart;

                    refreshItemsFetched = false;
                    refreshCount = undefined;
                    keyFetchIDs = {};
                    refreshKeyMap = {};
                    refreshIndexMap = {};
                    refreshIndexMap[-1] = refreshStart;
                    deletedKeys = {};
                }

                function beginRefresh() {
                    if (refreshRequested) {
                        // There's already a refresh that has yet to start
                        return;
                    }

                    refreshRequested = true;

                    setStatus(DataSourceStatus.waiting);

                    if (waitForRefresh) {
                        waitForRefresh = false;

                        // The edit queue has been paused until the next refresh - resume it now
                        applyNextEdit();
                        return;
                    }

                    if (editsQueued) {
                        // The refresh will be started once the edit queue empties out
                        return;
                    }

                    var refreshID = ++currentRefreshID;
                    refreshInProgress = true;
                    refreshFetchesInProgress = 0;

                    // Batch calls to beginRefresh
                    Scheduler.schedule(function VDS_async_beginRefresh() {
                        if (currentRefreshID !== refreshID) {
                            return;
                        }

                        refreshRequested = false;

                        resetRefreshState();

                        // Remove all slots that aren't live, so we don't waste time fetching them
                        for (var slot = slotsStart.next; slot !== slotsEnd;) {
                            var slotNext = slot.next;

                            if (!slotLive(slot) && slot !== slotListEnd) {
                                deleteUnnecessarySlot(slot);
                            }

                            slot = slotNext;
                        }

                        startRefreshFetches();
                    }, Scheduler.Priority.high, null, "WinJS.VirtualizedDataSource.beginRefresh");
                }

                function requestRefresh() {
                    refreshSignal = refreshSignal || new _Signal();

                    beginRefresh();

                    return refreshSignal.promise;
                }

                function resultsValidForRefresh(refreshID, fetchID) {
                    // This fetch has completed, whatever it has returned
                    delete fetchesInProgress[fetchID];

                    if (refreshID !== currentRefreshID) {
                        // This information is out of date.  Ignore it.
                        return false;
                    }

                    refreshFetchesInProgress--;

                    return true;
                }

                function fetchItemsForRefresh(key, fromStart, fetchID, promiseItems, index) {
                    var refreshID = currentRefreshID;

                    refreshFetchesInProgress++;

                    promiseItems.then(function (fetchResult) {
                        if (fetchResult.items && fetchResult.items.length) {
                            var perfID = "itemsFetched id=" + fetchID + " count=" + fetchResult.items.length;
                            profilerMarkStart(perfID);
                            if (resultsValidForRefresh(refreshID, fetchID)) {
                                addMarkers(fetchResult);
                                processRefreshResults(key, fetchResult.items, fetchResult.offset, fetchResult.totalCount, (typeof index === "number" ? index : fetchResult.absoluteIndex));
                            }
                            profilerMarkEnd(perfID);
                        } else {
                            return Promise.wrapError(new _ErrorFromName(FetchError.doesNotExist));
                        }
                    }).then(null, function (error) {
                        if (resultsValidForRefresh(refreshID, fetchID)) {
                            processRefreshErrorResult(key, fromStart, error);
                        }
                    });
                }

                function refreshRange(slot, fetchID, countBefore, countAfter) {
                    if (itemsFromKey) {
                        // Keys are the preferred identifiers when the item might have moved
                        fetchItemsForRefresh(slot.key, false, fetchID, itemsFromKey(fetchID, slot.key, countBefore, countAfter, slot.hints));
                    } else {
                        // Request additional items to try to locate items that have moved
                        var searchDelta = 10,
                            index = slot.index;

                        if (refreshIndexMap[index] && refreshIndexMap[index].firstInSequence) {
                            // Ensure at least one element is observed before this one
                            fetchItemsForRefresh(slot.key, false, fetchID, itemsFromIndex(fetchID, index - 1, Math.min(countBefore + searchDelta, index) - 1, countAfter + 1 + searchDelta), index - 1);
                        } else if (refreshIndexMap[index] && refreshIndexMap[index].lastInSequence) {
                            // Ask for the next index we need directly
                            fetchItemsForRefresh(slot.key, false, fetchID, itemsFromIndex(fetchID, index + 1, Math.min(countBefore + searchDelta, index) + 1, countAfter - 1 + searchDelta), index + 1);
                        } else {
                            fetchItemsForRefresh(slot.key, false, fetchID, itemsFromIndex(fetchID, index, Math.min(countBefore + searchDelta, index), countAfter + searchDelta), index);
                        }
                    }
                }

                function refreshFirstItem(fetchID) {
                    if (itemsFromStart) {
                        fetchItemsForRefresh(null, true, fetchID, itemsFromStart(fetchID, 1), 0);
                    } else if (itemsFromIndex) {
                        fetchItemsForRefresh(null, true, fetchID, itemsFromIndex(fetchID, 0, 0, 0), 0);
                    }
                }

                function keyFetchInProgress(key) {
                    return fetchesInProgress[keyFetchIDs[key]];
                }

                function refreshRanges(slotFirst, allRanges) {
                    // Fetch a few extra items each time, to catch insertions without requiring an extra fetch
                    var refreshFetchExtra = 3;

                    var refreshID = currentRefreshID;

                    var slotFetchFirst,
                        slotRefreshFirst,
                        fetchCount = 0,
                        fetchID;

                    // Walk through the slot list looking for keys we haven't fetched or attempted to fetch yet.  Rely on the
                    // heuristic that items that were close together before the refresh are likely to remain so after, so batched
                    // fetches will locate most of the previously fetched items.
                    for (var slot = slotFirst; slot !== slotsEnd; slot = slot.next) {
                        if (!slotFetchFirst && slot.key && !deletedKeys[slot.key] && !keyFetchInProgress(slot.key)) {
                            var slotRefresh = refreshKeyMap[slot.key];

                            // Keep attempting to fetch an item until at least one item on either side of it has been observed, so
                            // we can determine its position relative to others.
                            if (!slotRefresh || slotRefresh.firstInSequence || slotRefresh.lastInSequence) {
                                slotFetchFirst = slot;
                                slotRefreshFirst = slotRefresh;
                                fetchID = newFetchID();
                            }
                        }

                        if (!slotFetchFirst) {
                            // Also attempt to fetch placeholders for requests for specific keys, just in case those items no
                            // longer exist.
                            if (slot.key && isPlaceholder(slot) && !deletedKeys[slot.key]) {
                                // Fulfill each "itemFromKey" request
                                if (!refreshKeyMap[slot.key]) {
                                    // Fetch at least one item before and after, just to verify item's position in list
                                    fetchID = newFetchID();
                                    fetchItemsForRefresh(slot.key, false, fetchID, itemsFromKey(fetchID, slot.key, 1, 1, slot.hints));
                                }
                            }
                        } else {
                            var keyAlreadyFetched = keyFetchInProgress(slot.key);

                            if (!deletedKeys[slot.key] && !refreshKeyMap[slot.key] && !keyAlreadyFetched) {
                                if (slot.key) {
                                    keyFetchIDs[slot.key] = fetchID;
                                }
                                fetchCount++;
                            }

                            if (slot.lastInSequence || slot.next === slotListEnd || keyAlreadyFetched) {
                                refreshRange(slotFetchFirst, fetchID, (!slotRefreshFirst || slotRefreshFirst.firstInSequence ? refreshFetchExtra : 0), fetchCount - 1 + refreshFetchExtra);

                                if (!allRanges) {
                                    break;
                                }

                                slotFetchFirst = null;
                                fetchCount = 0;
                            }
                        }
                    }

                    if (refreshFetchesInProgress === 0 && !refreshItemsFetched && currentRefreshID === refreshID) {
                        // If nothing was successfully fetched, try fetching the first item, to detect an empty list
                        refreshFirstItem(newFetchID());
                    }

                }

                function startRefreshFetches() {
                    var refreshID = currentRefreshID;

                    do {
                        synchronousProgress = false;
                        reentrantContinue = true;
                        refreshRanges(slotsStart.next, true);
                        reentrantContinue = false;
                    } while (refreshFetchesInProgress === 0 && synchronousProgress && currentRefreshID === refreshID && refreshInProgress);

                    if (refreshFetchesInProgress === 0 && currentRefreshID === refreshID) {
                        concludeRefresh();
                    }
                }

                function continueRefresh(key) {
                    var refreshID = currentRefreshID;

                    // If the key is absent, then the attempt to fetch the first item just completed, and there is nothing else to
                    // fetch.
                    if (key) {
                        var slotContinue = keyMap[key];
                        if (!slotContinue) {
                            // In a rare case, the slot might have been deleted; just start scanning from the beginning again
                            slotContinue = slotsStart.next;
                        }

                        do {
                            synchronousRefresh = false;
                            reentrantRefresh = true;
                            refreshRanges(slotContinue, false);
                            reentrantRefresh = false;
                        } while (synchronousRefresh && currentRefreshID === refreshID && refreshInProgress);
                    }

                    if (reentrantContinue) {
                        synchronousProgress = true;
                    } else {
                        if (refreshFetchesInProgress === 0 && currentRefreshID === refreshID) {
                            // Walk through the entire list one more time, in case any edits were made during the refresh
                            startRefreshFetches();
                        }
                    }
                }

                function slotRefreshFromResult(result) {
                    if (typeof result !== "object" || !result) {
                        throw new _ErrorFromName("WinJS.UI.ListDataSource.InvalidItemReturned", strings.invalidItemReturned);
                    } else if (result === startMarker) {
                        return refreshStart;
                    } else if (result === endMarker) {
                        return refreshEnd;
                    } else if (!result.key) {
                        throw new _ErrorFromName("WinJS.UI.ListDataSource.InvalidKeyReturned", strings.invalidKeyReturned);
                    } else {
                        return refreshKeyMap[result.key];
                    }
                }

                function processRefreshSlotIndex(slot, expectedIndex) {
                    while (slot.index === undefined) {
                        setSlotIndex(slot, expectedIndex, refreshIndexMap);

                        if (slot.firstInSequence) {
                            return true;
                        }

                        slot = slot.prev;
                        expectedIndex--;
                    }

                    if (slot.index !== expectedIndex) {
                        // Something has changed since the refresh began; start again
                        beginRefresh();
                        return false;
                    }

                    return true;
                }

                function setRefreshSlotResult(slotRefresh, result) {
                    slotRefresh.key = result.key;
                    refreshKeyMap[slotRefresh.key] = slotRefresh;

                    slotRefresh.item = result;
                }

                // Returns the slot after the last insertion point between sequences
                function lastRefreshInsertionPoint() {
                    var slotNext = refreshEnd;
                    while (!slotNext.firstInSequence) {
                        slotNext = slotNext.prev;

                        if (slotNext === refreshStart) {
                            return null;
                        }
                    }

                    return slotNext;
                }

                function processRefreshResults(key, results, offset, count, index) {
                    index = validateIndexReturned(index);
                    count = validateCountReturned(count);

                    var keyPresent = false;

                    refreshItemsFetched = true;

                    var indexFirst = index - offset,
                        result = results[0];

                    if (result.key === key) {
                        keyPresent = true;
                    }

                    var slot = slotRefreshFromResult(result);
                    if (!slot) {
                        if (refreshIndexMap[indexFirst]) {
                            // Something has changed since the refresh began; start again
                            beginRefresh();
                            return;
                        }

                        // See if these results should be appended to an existing sequence
                        var slotPrev;
                        if (index !== undefined && (slotPrev = refreshIndexMap[indexFirst - 1])) {
                            if (!slotPrev.lastInSequence) {
                                // Something has changed since the refresh began; start again
                                beginRefresh();
                                return;
                            }
                            slot = addSlotAfter(slotPrev, refreshIndexMap);
                        } else {
                            // Create a new sequence
                            var slotSuccessor = (
                                +indexFirst === indexFirst ?
                                    successorFromIndex(indexFirst, refreshIndexMap, refreshStart, refreshEnd) :
                                    lastRefreshInsertionPoint(refreshStart, refreshEnd)
                            );

                            if (!slotSuccessor) {
                                // Something has changed since the refresh began; start again
                                beginRefresh();
                                return;
                            }

                            slot = createSlotSequence(slotSuccessor, indexFirst, refreshIndexMap);
                        }

                        setRefreshSlotResult(slot, results[0]);
                    } else {
                        if (+indexFirst === indexFirst) {
                            if (!processRefreshSlotIndex(slot, indexFirst)) {
                                return;
                            }
                        }
                    }

                    var resultsCount = results.length;
                    for (var i = 1; i < resultsCount; i++) {
                        result = results[i];

                        if (result.key === key) {
                            keyPresent = true;
                        }

                        var slotNext = slotRefreshFromResult(result);

                        if (!slotNext) {
                            if (!slot.lastInSequence) {
                                // Something has changed since the refresh began; start again
                                beginRefresh();
                                return;
                            }
                            slotNext = addSlotAfter(slot, refreshIndexMap);
                            setRefreshSlotResult(slotNext, result);
                        } else {
                            if (slot.index !== undefined && !processRefreshSlotIndex(slotNext, slot.index + 1)) {
                                return;
                            }

                            // If the slots aren't adjacent, see if it's possible to reorder sequences to make them so
                            if (slotNext !== slot.next) {
                                if (!slot.lastInSequence || !slotNext.firstInSequence) {
                                    // Something has changed since the refresh began; start again
                                    beginRefresh();
                                    return;
                                }

                                var slotLast = sequenceEnd(slotNext);
                                if (slotLast !== refreshEnd) {
                                    moveSequenceAfter(slot, slotNext, slotLast);
                                } else {
                                    var slotFirst = sequenceStart(slot);
                                    if (slotFirst !== refreshStart) {
                                        moveSequenceBefore(slotNext, slotFirst, slot);
                                    } else {
                                        // Something has changed since the refresh began; start again
                                        beginRefresh();
                                        return;
                                    }
                                }

                                mergeSequences(slot);
                            } else if (slot.lastInSequence) {
                                mergeSequences(slot);
                            }
                        }

                        slot = slotNext;
                    }

                    if (!keyPresent) {
                        deletedKeys[key] = true;
                    }

                    // If the count wasn't provided, see if it can be determined from the end of the list.
                    if (!isNonNegativeNumber(count) && !refreshEnd.firstInSequence) {
                        var indexLast = refreshEnd.prev.index;
                        if (indexLast !== undefined) {
                            count = indexLast + 1;
                        }
                    }

                    if (isNonNegativeNumber(count) || count === CountResult.unknown) {
                        if (isNonNegativeNumber(refreshCount)) {
                            if (count !== refreshCount) {
                                // Something has changed since the refresh began; start again
                                beginRefresh();
                                return;
                            }
                        } else {
                            refreshCount = count;
                        }

                        if (isNonNegativeNumber(refreshCount) && !refreshIndexMap[refreshCount]) {
                            setSlotIndex(refreshEnd, refreshCount, refreshIndexMap);
                        }
                    }

                    if (reentrantRefresh) {
                        synchronousRefresh = true;
                    } else {
                        continueRefresh(key);
                    }
                }

                function processRefreshErrorResult(key, fromStart, error) {
                    switch (error.name) {
                        case FetchError.noResponse:
                            setStatus(DataSourceStatus.failure);
                            break;

                        case FetchError.doesNotExist:
                            if (fromStart) {
                                // The attempt to fetch the first item failed, so the list must be empty
                                setSlotIndex(refreshEnd, 0, refreshIndexMap);
                                refreshCount = 0;

                                concludeRefresh();
                            } else {
                                deletedKeys[key] = true;

                                if (reentrantRefresh) {
                                    synchronousRefresh = true;
                                } else {
                                    continueRefresh(key);
                                }
                            }
                            break;
                    }
                }

                function slotFromSlotRefresh(slotRefresh) {
                    if (slotRefresh === refreshStart) {
                        return slotsStart;
                    } else if (slotRefresh === refreshEnd) {
                        return slotListEnd;
                    } else {
                        return keyMap[slotRefresh.key];
                    }
                }

                function slotRefreshFromSlot(slot) {
                    if (slot === slotsStart) {
                        return refreshStart;
                    } else if (slot === slotListEnd) {
                        return refreshEnd;
                    } else {
                        return refreshKeyMap[slot.key];
                    }
                }

                function mergeSequencesForRefresh(slotPrev) {
                    mergeSequences(slotPrev);

                    // Mark the merge point, so we can distinguish insertions from unrequested items
                    slotPrev.next.mergedForRefresh = true;
                }

                function copyRefreshSlotData(slotRefresh, slot) {
                    setSlotKey(slot, slotRefresh.key);
                    slot.itemNew = slotRefresh.item;
                }

                function addNewSlotFromRefresh(slotRefresh, slotNext, insertAfter) {
                    var slotNew = createPrimarySlot();

                    copyRefreshSlotData(slotRefresh, slotNew);
                    insertAndMergeSlot(slotNew, slotNext, insertAfter, !insertAfter);

                    var index = slotRefresh.index;
                    if (+index !== index) {
                        index = (insertAfter ? slotNew.prev.index + 1 : slotNext.next.index - 1);
                    }

                    setSlotIndex(slotNew, index, indexMap);

                    return slotNew;
                }

                function matchSlotForRefresh(slotExisting, slot, slotRefresh) {
                    if (slotExisting) {
                        sendMirageNotifications(slotExisting, slot, slotExisting.bindingMap);
                        mergeSlotsAndItem(slotExisting, slot, slotRefresh.item);
                    } else {
                        copyRefreshSlotData(slotRefresh, slot);

                        // If the index was requested, complete the promises now, as the index might be about to change
                        if (slot.indexRequested) {
                            updateSlotItem(slot);
                        }
                    }
                }

                function updateSlotForRefresh(slotExisting, slot, slotRefresh) {
                    if (!slot.key) {
                        if (slotExisting) {
                            // Record the relationship between the slot to discard and its neighbors
                            slotRefresh.mergeWithPrev = !slot.firstInSequence;
                            slotRefresh.mergeWithNext = !slot.lastInSequence;
                        } else {
                            slotRefresh.stationary = true;
                        }
                        matchSlotForRefresh(slotExisting, slot, slotRefresh);
                        return true;
                    } else {
                        return false;
                    }
                }

                function indexForRefresh(slot) {
                    var indexNew;

                    if (slot.indexRequested) {
                        indexNew = slot.index;
                    } else {
                        var slotRefresh = slotRefreshFromSlot(slot);
                        if (slotRefresh) {
                            indexNew = slotRefresh.index;
                        }
                    }

                    return indexNew;
                }

                function concludeRefresh() {
                    beginRefreshCount = 0;
                    refreshHistory = new Array(100);
                    refreshHistoryPos = -1;

                    indexUpdateDeferred = true;

                    keyFetchIDs = {};

                    var i,
                        j,
                        slot,
                        slotPrev,
                        slotNext,
                        slotBefore,
                        slotAfter,
                        slotRefresh,
                        slotExisting,
                        slotFirstInSequence,
                        sequenceCountOld,
                        sequencesOld = [],
                        sequenceOld,
                        sequenceOldPrev,
                        sequenceOldBestMatch,
                        sequenceCountNew,
                        sequencesNew = [],
                        sequenceNew,
                        index,
                        offset;

                    // Assign a sequence number to each refresh slot
                    sequenceCountNew = 0;
                    for (slotRefresh = refreshStart; slotRefresh; slotRefresh = slotRefresh.next) {
                        slotRefresh.sequenceNumber = sequenceCountNew;

                        if (slotRefresh.firstInSequence) {
                            slotFirstInSequence = slotRefresh;
                        }

                        if (slotRefresh.lastInSequence) {
                            sequencesNew[sequenceCountNew] = {
                                first: slotFirstInSequence,
                                last: slotRefresh,
                                matchingItems: 0
                            };
                            sequenceCountNew++;
                        }
                    }

                    // Remove unnecessary information from main slot list, and update the items
                    lastSlotReleased = null;
                    releasedSlots = 0;
                    for (slot = slotsStart.next; slot !== slotsEnd;) {
                        slotRefresh = refreshKeyMap[slot.key];
                        slotNext = slot.next;

                        if (slot !== slotListEnd) {
                            if (!slotLive(slot)) {
                                // Some more items might have been released since the refresh started.  Strip them away from the
                                // main slot list, as they'll just get in the way from now on.  Since we're discarding these, but
                                // don't know if they're actually going away, split the sequence as our starting assumption must be
                                // that the items on either side are in separate sequences.
                                deleteUnnecessarySlot(slot);
                            } else if (slot.key && !slotRefresh) {
                                // Remove items that have been deleted (or moved far away) and send removed notifications
                                deleteSlot(slot, false);
                            } else if (refreshCount === 0 || (slot.indexRequested && slot.index >= refreshCount)) {
                                // Remove items that can't exist in the list and send mirage removed notifications
                                deleteSlot(slot, true);
                            } else if (slot.item || slot.keyRequested) {
                                // Store the new item; this value will be compared with that stored in slot.item later
                                slot.itemNew = slotRefresh.item;
                            } else {
                                // Clear keys and items that have never been observed by client
                                if (slot.key) {
                                    if (!slot.keyRequested) {
                                        delete keyMap[slot.key];
                                        delete slot.key;
                                    }
                                    slot.itemNew = null;
                                }
                            }
                        }

                        slot = slotNext;
                    }

                    // Placeholders generated by itemsAtIndex should not move.  Match these to items now if possible, or merge them
                    // with existing items if necessary.
                    for (slot = slotsStart.next; slot !== slotListEnd;) {
                        slotNext = slot.next;

                        if (slot.indexRequested) {
                            slotRefresh = refreshIndexMap[slot.index];
                            if (slotRefresh) {
                                matchSlotForRefresh(slotFromSlotRefresh(slotRefresh), slot, slotRefresh);
                            }
                        }

                        slot = slotNext;
                    }

                    // Match old sequences to new sequences
                    var bestMatch,
                        bestMatchCount,
                        previousBestMatch = 0,
                        newSequenceCounts = [],
                        slotIndexRequested,
                        sequenceIndexEnd,
                        sequenceOldEnd;

                    sequenceCountOld = 0;
                    for (slot = slotsStart; slot !== slotsEnd; slot = slot.next) {
                        if (slot.firstInSequence) {
                            slotFirstInSequence = slot;
                            slotIndexRequested = null;
                            for (i = 0; i < sequenceCountNew; i++) {
                                newSequenceCounts[i] = 0;
                            }
                        }

                        if (slot.indexRequested) {
                            slotIndexRequested = slot;
                        }

                        slotRefresh = slotRefreshFromSlot(slot);
                        if (slotRefresh) {
                            newSequenceCounts[slotRefresh.sequenceNumber]++;
                        }

                        if (slot.lastInSequence) {
                            // Determine which new sequence is the best match for this old one.  Use a simple greedy algorithm to
                            // ensure the relative ordering of matched sequences is the same; out-of-order sequences will require
                            // move notifications.
                            bestMatchCount = 0;
                            for (i = previousBestMatch; i < sequenceCountNew; i++) {
                                if (bestMatchCount < newSequenceCounts[i]) {
                                    bestMatchCount = newSequenceCounts[i];
                                    bestMatch = i;
                                }
                            }

                            sequenceOld = {
                                first: slotFirstInSequence,
                                last: slot,
                                sequenceNew: (bestMatchCount > 0 ? sequencesNew[bestMatch] : undefined),
                                matchingItems: bestMatchCount
                            };

                            if (slotIndexRequested) {
                                sequenceOld.indexRequested = true;
                                sequenceOld.stationarySlot = slotIndexRequested;
                            }

                            sequencesOld[sequenceCountOld] = sequenceOld;

                            if (slot === slotListEnd) {
                                sequenceIndexEnd = sequenceCountOld;
                                sequenceOldEnd = sequenceOld;
                            }

                            sequenceCountOld++;

                            if (sequencesNew[bestMatch].first.index !== undefined) {
                                previousBestMatch = bestMatch;
                            }
                        }
                    }

                    // Special case: split the old start into a separate sequence if the new start isn't its best match
                    if (sequencesOld[0].sequenceNew !== sequencesNew[0]) {
                        splitSequence(slotsStart);
                        sequencesOld[0].first = slotsStart.next;
                        sequencesOld.unshift({
                            first: slotsStart,
                            last: slotsStart,
                            sequenceNew: sequencesNew[0],
                            matchingItems: 1
                        });
                        sequenceIndexEnd++;
                        sequenceCountOld++;
                    }

                    var listEndObserved = !slotListEnd.firstInSequence;

                    // Special case: split the old end into a separate sequence if the new end isn't its best match
                    if (sequenceOldEnd.sequenceNew !== sequencesNew[sequenceCountNew - 1]) {
                        splitSequence(slotListEnd.prev);
                        sequenceOldEnd.last = slotListEnd.prev;
                        sequenceIndexEnd++;
                        sequencesOld.splice(sequenceIndexEnd, 0, {
                            first: slotListEnd,
                            last: slotListEnd,
                            sequenceNew: sequencesNew[sequenceCountNew - 1],
                            matchingItems: 1
                        });
                        sequenceCountOld++;
                        sequenceOldEnd = sequencesOld[sequenceIndexEnd];
                    }

                    // Map new sequences to old sequences
                    for (i = 0; i < sequenceCountOld; i++) {
                        sequenceNew = sequencesOld[i].sequenceNew;
                        if (sequenceNew && sequenceNew.matchingItems < sequencesOld[i].matchingItems) {
                            sequenceNew.matchingItems = sequencesOld[i].matchingItems;
                            sequenceNew.sequenceOld = sequencesOld[i];
                        }
                    }

                    // The old end must always be the best match for the new end (if the new end is also the new start, they will
                    // be merged below).
                    sequencesNew[sequenceCountNew - 1].sequenceOld = sequenceOldEnd;
                    sequenceOldEnd.stationarySlot = slotListEnd;

                    // The old start must always be the best match for the new start
                    sequencesNew[0].sequenceOld = sequencesOld[0];
                    sequencesOld[0].stationarySlot = slotsStart;

                    // Merge additional indexed old sequences when possible

                    // First do a forward pass
                    for (i = 0; i <= sequenceIndexEnd; i++) {
                        sequenceOld = sequencesOld[i];

                        if (sequenceOld.sequenceNew && (sequenceOldBestMatch = sequenceOld.sequenceNew.sequenceOld) === sequenceOldPrev && sequenceOldPrev.last !== slotListEnd) {
                            mergeSequencesForRefresh(sequenceOldBestMatch.last);
                            sequenceOldBestMatch.last = sequenceOld.last;
                            delete sequencesOld[i];
                        } else {
                            sequenceOldPrev = sequenceOld;
                        }
                    }

                    // Now do a reverse pass
                    sequenceOldPrev = null;
                    for (i = sequenceIndexEnd; i >= 0; i--) {
                        sequenceOld = sequencesOld[i];
                        // From this point onwards, some members of sequencesOld may be undefined
                        if (sequenceOld) {
                            if (sequenceOld.sequenceNew && (sequenceOldBestMatch = sequenceOld.sequenceNew.sequenceOld) === sequenceOldPrev && sequenceOld.last !== slotListEnd) {
                                mergeSequencesForRefresh(sequenceOld.last);
                                sequenceOldBestMatch.first = sequenceOld.first;
                                delete sequencesOld[i];
                            } else {
                                sequenceOldPrev = sequenceOld;
                            }
                        }
                    }

                    // Since we may have forced the list end into a separate sequence, the mergedForRefresh flag may be incorrect
                    if (listEndObserved) {
                        delete slotListEnd.mergedForRefresh;
                    }

                    var sequencePairsToMerge = [];

                    // Find unchanged sequences without indices that can be merged with existing sequences without move
                    // notifications.
                    for (i = sequenceIndexEnd + 1; i < sequenceCountOld; i++) {
                        sequenceOld = sequencesOld[i];
                        if (sequenceOld && (!sequenceOld.sequenceNew || sequenceOld.sequenceNew.sequenceOld !== sequenceOld)) {
                            // If the order of the known items in the sequence is unchanged, then the sequence probably has not
                            // moved, but we now know where it belongs relative to at least one other sequence.
                            var orderPreserved = true,
                                slotRefreshFirst = null,
                                slotRefreshLast = null,
                                sequenceLength = 0;
                            slotRefresh = slotRefreshFromSlot(sequenceOld.first);
                            if (slotRefresh) {
                                slotRefreshFirst = slotRefreshLast = slotRefresh;
                                sequenceLength = 1;
                            }
                            for (slot = sequenceOld.first; slot !== sequenceOld.last; slot = slot.next) {
                                var slotRefreshNext = slotRefreshFromSlot(slot.next);

                                if (slotRefresh && slotRefreshNext && (slotRefresh.lastInSequence || slotRefresh.next !== slotRefreshNext)) {
                                    orderPreserved = false;
                                    break;
                                }

                                if (slotRefresh && !slotRefreshFirst) {
                                    slotRefreshFirst = slotRefreshLast = slotRefresh;
                                }

                                if (slotRefreshNext && slotRefreshFirst) {
                                    slotRefreshLast = slotRefreshNext;
                                    sequenceLength++;
                                }

                                slotRefresh = slotRefreshNext;
                            }

                            // If the stationary sequence has indices, verify that there is enough space for this sequence - if
                            // not, then something somewhere has moved after all.
                            if (orderPreserved && slotRefreshFirst && slotRefreshFirst.index !== undefined) {
                                var indexBefore;
                                if (!slotRefreshFirst.firstInSequence) {
                                    slotBefore = slotFromSlotRefresh(slotRefreshFirst.prev);
                                    if (slotBefore) {
                                        indexBefore = slotBefore.index;
                                    }
                                }

                                var indexAfter;
                                if (!slotRefreshLast.lastInSequence) {
                                    slotAfter = slotFromSlotRefresh(slotRefreshLast.next);
                                    if (slotAfter) {
                                        indexAfter = slotAfter.index;
                                    }
                                }

                                if ((!slotAfter || slotAfter.lastInSequence || slotAfter.mergedForRefresh) &&
                                        (indexBefore === undefined || indexAfter === undefined || indexAfter - indexBefore - 1 >= sequenceLength)) {
                                    sequenceOld.locationJustDetermined = true;

                                    // Mark the individual refresh slots as not requiring move notifications
                                    for (slotRefresh = slotRefreshFirst; ; slotRefresh = slotRefresh.next) {
                                        slotRefresh.locationJustDetermined = true;

                                        if (slotRefresh === slotRefreshLast) {
                                            break;
                                        }
                                    }

                                    // Store any adjacent placeholders so they can be merged once the moves and insertions have
                                    // been processed.
                                    var slotFirstInSequence = slotFromSlotRefresh(slotRefreshFirst),
                                        slotLastInSequence = slotFromSlotRefresh(slotRefreshLast);
                                    sequencePairsToMerge.push({
                                        slotBeforeSequence: (slotFirstInSequence.firstInSequence ? null : slotFirstInSequence.prev),
                                        slotFirstInSequence: slotFirstInSequence,
                                        slotLastInSequence: slotLastInSequence,
                                        slotAfterSequence: (slotLastInSequence.lastInSequence ? null : slotLastInSequence.next)
                                    });
                                }
                            }
                        }
                    }

                    // Remove placeholders in old sequences that don't map to new sequences (and don't contain requests for a
                    // specific index or key), as they no longer have meaning.
                    for (i = 0; i < sequenceCountOld; i++) {
                        sequenceOld = sequencesOld[i];
                        if (sequenceOld && !sequenceOld.indexRequested && !sequenceOld.locationJustDetermined && (!sequenceOld.sequenceNew || sequenceOld.sequenceNew.sequenceOld !== sequenceOld)) {
                            sequenceOld.sequenceNew = null;

                            slot = sequenceOld.first;

                            var sequenceEndReached;
                            do {
                                sequenceEndReached = (slot === sequenceOld.last);

                                slotNext = slot.next;

                                if (slot !== slotsStart && slot !== slotListEnd && slot !== slotsEnd && !slot.item && !slot.keyRequested) {
                                    deleteSlot(slot, true);
                                    if (sequenceOld.first === slot) {
                                        if (sequenceOld.last === slot) {
                                            delete sequencesOld[i];
                                            break;
                                        } else {
                                            sequenceOld.first = slot.next;
                                        }
                                    } else if (sequenceOld.last === slot) {
                                        sequenceOld.last = slot.prev;
                                    }
                                }

                                slot = slotNext;
                            } while (!sequenceEndReached);
                        }
                    }

                    // Locate boundaries of new items in new sequences
                    for (i = 0; i < sequenceCountNew; i++) {
                        sequenceNew = sequencesNew[i];
                        for (slotRefresh = sequenceNew.first; !slotFromSlotRefresh(slotRefresh) && !slotRefresh.lastInSequence; slotRefresh = slotRefresh.next) {
                            /*@empty*/
                        }
                        if (slotRefresh.lastInSequence && !slotFromSlotRefresh(slotRefresh)) {
                            sequenceNew.firstInner = sequenceNew.lastInner = null;
                        } else {
                            sequenceNew.firstInner = slotRefresh;
                            for (slotRefresh = sequenceNew.last; !slotFromSlotRefresh(slotRefresh) ; slotRefresh = slotRefresh.prev) {
                                /*@empty*/
                            }
                            sequenceNew.lastInner = slotRefresh;
                        }
                    }

                    // Determine which items to move
                    for (i = 0; i < sequenceCountNew; i++) {
                        sequenceNew = sequencesNew[i];
                        if (sequenceNew && sequenceNew.firstInner) {
                            sequenceOld = sequenceNew.sequenceOld;
                            if (sequenceOld) {
                                // Number the slots in each new sequence with their offset in the corresponding old sequence (or
                                // undefined if in a different old sequence).
                                var ordinal = 0;
                                for (slot = sequenceOld.first; true; slot = slot.next, ordinal++) {
                                    slotRefresh = slotRefreshFromSlot(slot);
                                    if (slotRefresh && slotRefresh.sequenceNumber === sequenceNew.firstInner.sequenceNumber) {
                                        slotRefresh.ordinal = ordinal;
                                    }

                                    if (slot.lastInSequence) {
                                        break;
                                    }
                                }

                                // Determine longest subsequence of items that are in the same order before and after
                                var piles = [];
                                for (slotRefresh = sequenceNew.firstInner; true; slotRefresh = slotRefresh.next) {
                                    ordinal = slotRefresh.ordinal;
                                    if (ordinal !== undefined) {
                                        var searchFirst = 0,
                                            searchLast = piles.length - 1;
                                        while (searchFirst <= searchLast) {
                                            var searchMidpoint = Math.floor(0.5 * (searchFirst + searchLast));
                                            if (piles[searchMidpoint].ordinal < ordinal) {
                                                searchFirst = searchMidpoint + 1;
                                            } else {
                                                searchLast = searchMidpoint - 1;
                                            }
                                        }
                                        piles[searchFirst] = slotRefresh;
                                        if (searchFirst > 0) {
                                            slotRefresh.predecessor = piles[searchFirst - 1];
                                        }
                                    }

                                    if (slotRefresh === sequenceNew.lastInner) {
                                        break;
                                    }
                                }

                                // The items in the longest ordered subsequence don't move; everything else does
                                var stationaryItems = [],
                                    stationaryItemCount = piles.length;
                                slotRefresh = piles[stationaryItemCount - 1];
                                for (j = stationaryItemCount; j--;) {
                                    slotRefresh.stationary = true;
                                    stationaryItems[j] = slotRefresh;
                                    slotRefresh = slotRefresh.predecessor;
                                }
                                sequenceOld.stationarySlot = slotFromSlotRefresh(stationaryItems[0]);

                                // Try to match new items before the first stationary item to placeholders
                                slotRefresh = stationaryItems[0];
                                slot = slotFromSlotRefresh(slotRefresh);
                                slotPrev = slot.prev;
                                var sequenceBoundaryReached = slot.firstInSequence;
                                while (!slotRefresh.firstInSequence) {
                                    slotRefresh = slotRefresh.prev;
                                    slotExisting = slotFromSlotRefresh(slotRefresh);
                                    if (!slotExisting || slotRefresh.locationJustDetermined) {
                                        // Find the next placeholder walking backwards
                                        while (!sequenceBoundaryReached && slotPrev !== slotsStart) {
                                            slot = slotPrev;
                                            slotPrev = slot.prev;
                                            sequenceBoundaryReached = slot.firstInSequence;

                                            if (updateSlotForRefresh(slotExisting, slot, slotRefresh)) {
                                                break;
                                            }
                                        }
                                    }
                                }

                                // Try to match new items between stationary items to placeholders
                                for (j = 0; j < stationaryItemCount - 1; j++) {
                                    slotRefresh = stationaryItems[j];
                                    slot = slotFromSlotRefresh(slotRefresh);
                                    var slotRefreshStop = stationaryItems[j + 1],
                                        slotRefreshMergePoint = null,
                                        slotStop = slotFromSlotRefresh(slotRefreshStop),
                                        slotExisting;
                                    // Find all the new items
                                    slotNext = slot.next;
                                    for (slotRefresh = slotRefresh.next; slotRefresh !== slotRefreshStop && !slotRefreshMergePoint && slot !== slotStop; slotRefresh = slotRefresh.next) {
                                        slotExisting = slotFromSlotRefresh(slotRefresh);
                                        if (!slotExisting || slotRefresh.locationJustDetermined) {
                                            // Find the next placeholder
                                            while (slotNext !== slotStop) {
                                                // If a merge point is reached, match the remainder of the placeholders by walking backwards
                                                if (slotNext.mergedForRefresh) {
                                                    slotRefreshMergePoint = slotRefresh.prev;
                                                    break;
                                                }

                                                slot = slotNext;
                                                slotNext = slot.next;

                                                if (updateSlotForRefresh(slotExisting, slot, slotRefresh)) {
                                                    break;
                                                }
                                            }
                                        }
                                    }

                                    // Walk backwards to the first merge point if necessary
                                    if (slotRefreshMergePoint) {
                                        slotPrev = slotStop.prev;
                                        for (slotRefresh = slotRefreshStop.prev; slotRefresh !== slotRefreshMergePoint && slotStop !== slot; slotRefresh = slotRefresh.prev) {
                                            slotExisting = slotFromSlotRefresh(slotRefresh);
                                            if (!slotExisting || slotRefresh.locationJustDetermined) {
                                                // Find the next placeholder walking backwards
                                                while (slotPrev !== slot) {
                                                    slotStop = slotPrev;
                                                    slotPrev = slotStop.prev;

                                                    if (updateSlotForRefresh(slotExisting, slotStop, slotRefresh)) {
                                                        break;
                                                    }
                                                }
                                            }
                                        }
                                    }

                                    // Delete remaining placeholders, sending notifications
                                    while (slotNext !== slotStop) {
                                        slot = slotNext;
                                        slotNext = slot.next;

                                        if (slot !== slotsStart && isPlaceholder(slot) && !slot.keyRequested) {
                                            // This might occur due to two sequences - requested by different clients - being
                                            // merged.  However, since only sequences with indices are merged, if this placehholder
                                            // is no longer necessary, it means an item actually was removed, so this doesn't count
                                            // as a mirage.
                                            deleteSlot(slot);
                                        }
                                    }
                                }

                                // Try to match new items after the last stationary item to placeholders
                                slotRefresh = stationaryItems[stationaryItemCount - 1];
                                slot = slotFromSlotRefresh(slotRefresh);
                                slotNext = slot.next;
                                sequenceBoundaryReached = slot.lastInSequence;
                                while (!slotRefresh.lastInSequence) {
                                    slotRefresh = slotRefresh.next;
                                    slotExisting = slotFromSlotRefresh(slotRefresh);
                                    if (!slotExisting || slotRefresh.locationJustDetermined) {
                                        // Find the next placeholder
                                        while (!sequenceBoundaryReached && slotNext !== slotListEnd) {
                                            slot = slotNext;
                                            slotNext = slot.next;
                                            sequenceBoundaryReached = slot.lastInSequence;

                                            if (updateSlotForRefresh(slotExisting, slot, slotRefresh)) {
                                                break;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }

                    // Move items and send notifications
                    for (i = 0; i < sequenceCountNew; i++) {
                        sequenceNew = sequencesNew[i];

                        if (sequenceNew.firstInner) {
                            slotPrev = null;
                            for (slotRefresh = sequenceNew.firstInner; true; slotRefresh = slotRefresh.next) {
                                slot = slotFromSlotRefresh(slotRefresh);
                                if (slot) {
                                    if (!slotRefresh.stationary) {
                                        var slotMoveBefore,
                                            mergeWithPrev = false,
                                            mergeWithNext = false;
                                        if (slotPrev) {
                                            slotMoveBefore = slotPrev.next;
                                            mergeWithPrev = true;
                                        } else {
                                            // The first item will be inserted before the first stationary item, so find that now
                                            var slotRefreshStationary;
                                            for (slotRefreshStationary = sequenceNew.firstInner; !slotRefreshStationary.stationary && slotRefreshStationary !== sequenceNew.lastInner; slotRefreshStationary = slotRefreshStationary.next) {
                                                /*@empty*/
                                            }

                                            if (!slotRefreshStationary.stationary) {
                                                // There are no stationary items, as all the items are moving from another old
                                                // sequence.

                                                index = slotRefresh.index;

                                                // Find the best place to insert the new sequence
                                                if (index === 0) {
                                                    // Index 0 is a special case
                                                    slotMoveBefore = slotsStart.next;
                                                    mergeWithPrev = true;
                                                } else if (index === undefined) {
                                                    slotMoveBefore = slotsEnd;
                                                } else {
                                                    // Use a linear search; unlike successorFromIndex, prefer the last insertion
                                                    // point between sequences over the precise index
                                                    slotMoveBefore = slotsStart.next;
                                                    var lastSequenceStart = null;
                                                    while (true) {
                                                        if (slotMoveBefore.firstInSequence) {
                                                            lastSequenceStart = slotMoveBefore;
                                                        }

                                                        if ((index < slotMoveBefore.index && lastSequenceStart) || slotMoveBefore === slotListEnd) {
                                                            break;
                                                        }

                                                        slotMoveBefore = slotMoveBefore.next;
                                                    }

                                                    if (!slotMoveBefore.firstInSequence && lastSequenceStart) {
                                                        slotMoveBefore = lastSequenceStart;
                                                    }
                                                }
                                            } else {
                                                slotMoveBefore = slotFromSlotRefresh(slotRefreshStationary);
                                                mergeWithNext = true;
                                            }
                                        }

                                        // Preserve merge boundaries
                                        if (slot.mergedForRefresh) {
                                            delete slot.mergedForRefresh;
                                            if (!slot.lastInSequence) {
                                                slot.next.mergedForRefresh = true;
                                            }
                                        }

                                        mergeWithPrev = mergeWithPrev || slotRefresh.mergeWithPrev;
                                        mergeWithNext = mergeWithNext || slotRefresh.mergeWithNext;

                                        var skipNotifications = slotRefresh.locationJustDetermined;

                                        moveSlot(slot, slotMoveBefore, mergeWithPrev, mergeWithNext, skipNotifications);

                                        if (skipNotifications && mergeWithNext) {
                                            // Since this item was moved without a notification, this is an implicit merge of
                                            // sequences.  Mark the item's successor as mergedForRefresh.
                                            slotMoveBefore.mergedForRefresh = true;
                                        }
                                    }

                                    slotPrev = slot;
                                }

                                if (slotRefresh === sequenceNew.lastInner) {
                                    break;
                                }
                            }
                        }
                    }

                    // Insert new items (with new indices) and send notifications
                    for (i = 0; i < sequenceCountNew; i++) {
                        sequenceNew = sequencesNew[i];

                        if (sequenceNew.firstInner) {
                            slotPrev = null;
                            for (slotRefresh = sequenceNew.firstInner; true; slotRefresh = slotRefresh.next) {
                                slot = slotFromSlotRefresh(slotRefresh);
                                if (!slot) {
                                    var slotInsertBefore;
                                    if (slotPrev) {
                                        slotInsertBefore = slotPrev.next;
                                    } else {
                                        // The first item will be inserted *before* the first old item, so find that now
                                        var slotRefreshOld;
                                        for (slotRefreshOld = sequenceNew.firstInner; !slotFromSlotRefresh(slotRefreshOld) ; slotRefreshOld = slotRefreshOld.next) {
                                            /*@empty*/
                                        }
                                        slotInsertBefore = slotFromSlotRefresh(slotRefreshOld);
                                    }

                                    // Create a new slot for the item
                                    slot = addNewSlotFromRefresh(slotRefresh, slotInsertBefore, !!slotPrev);

                                    var slotRefreshNext = slotRefreshFromSlot(slotInsertBefore);

                                    if (!slotInsertBefore.mergedForRefresh && (!slotRefreshNext || !slotRefreshNext.locationJustDetermined)) {
                                        prepareSlotItem(slot);

                                        // Send the notification after the insertion
                                        sendInsertedNotification(slot);
                                    }
                                }
                                slotPrev = slot;

                                if (slotRefresh === sequenceNew.lastInner) {
                                    break;
                                }
                            }
                        }
                    }

                    // Rebuild the indexMap from scratch, so it is possible to detect colliding indices
                    indexMap = [];

                    // Send indexChanged and changed notifications
                    var indexFirst = -1;
                    for (slot = slotsStart, offset = 0; slot !== slotsEnd; offset++) {
                        var slotNext = slot.next;

                        if (slot.firstInSequence) {
                            slotFirstInSequence = slot;
                            offset = 0;
                        }

                        if (indexFirst === undefined) {
                            var indexNew = indexForRefresh(slot);
                            if (indexNew !== undefined) {
                                indexFirst = indexNew - offset;
                            }
                        }

                        // See if the next slot would cause a contradiction, in which case split the sequences
                        if (indexFirst !== undefined && !slot.lastInSequence) {
                            var indexNewNext = indexForRefresh(slot.next);
                            if (indexNewNext !== undefined && indexNewNext !== indexFirst + offset + 1) {
                                splitSequence(slot);

                                // 'Move' the items in-place individually, so move notifications are sent.  In rare cases, this
                                // will result in multiple move notifications being sent for a given item, but that's fine.
                                var first = true;
                                for (var slotMove = slot.next, lastInSequence = false; !lastInSequence && slotMove !== slotListEnd;) {
                                    var slotMoveNext = slotMove.next;

                                    lastInSequence = slotMove.lastInSequence;

                                    moveSlot(slotMove, slotMoveNext, !first, false);

                                    first = false;
                                    slotMove = slotMoveNext;
                                }
                            }
                        }

                        if (slot.lastInSequence) {
                            index = indexFirst;
                            for (var slotUpdate = slotFirstInSequence; slotUpdate !== slotNext;) {
                                var slotUpdateNext = slotUpdate.next;

                                if (index >= refreshCount && slotUpdate !== slotListEnd) {
                                    deleteSlot(slotUpdate, true);
                                } else {
                                    var slotWithIndex = indexMap[index];

                                    if (index !== slotUpdate.index) {
                                        delete indexMap[index];
                                        changeSlotIndex(slotUpdate, index);
                                    } else if (+index === index && indexMap[index] !== slotUpdate) {
                                        indexMap[index] = slotUpdate;
                                    }

                                    if (slotUpdate.itemNew) {
                                        updateSlotItem(slotUpdate);
                                    }

                                    if (slotWithIndex) {
                                        // Two slots' indices have collided - merge them
                                        if (slotUpdate.key) {
                                            sendMirageNotifications(slotUpdate, slotWithIndex, slotUpdate.bindingMap);
                                            mergeSlots(slotUpdate, slotWithIndex);
                                            if (+index === index) {
                                                indexMap[index] = slotUpdate;
                                            }
                                        } else {
                                            sendMirageNotifications(slotWithIndex, slotUpdate, slotWithIndex.bindingMap);
                                            mergeSlots(slotWithIndex, slotUpdate);
                                            if (+index === index) {
                                                indexMap[index] = slotWithIndex;
                                            }
                                        }
                                    }

                                    if (+index === index) {
                                        index++;
                                    }
                                }

                                slotUpdate = slotUpdateNext;
                            }

                            indexFirst = undefined;
                        }

                        slot = slotNext;
                    }

                    // See if any sequences need to be moved and/or merged
                    var indexMax = -2,
                        listEndReached;

                    for (slot = slotsStart, offset = 0; slot !== slotsEnd; offset++) {
                        var slotNext = slot.next;

                        if (slot.firstInSequence) {
                            slotFirstInSequence = slot;
                            offset = 0;
                        }

                        // Clean up during this pass
                        delete slot.mergedForRefresh;

                        if (slot.lastInSequence) {
                            // Move sequence if necessary
                            if (slotFirstInSequence.index === undefined) {
                                slotBefore = slotFirstInSequence.prev;
                                var slotRefreshBefore;
                                if (slotBefore && (slotRefreshBefore = slotRefreshFromSlot(slotBefore)) && !slotRefreshBefore.lastInSequence &&
                                        (slotRefresh = slotRefreshFromSlot(slot)) && slotRefresh.prev === slotRefreshBefore) {
                                    moveSequenceAfter(slotBefore, slotFirstInSequence, slot);
                                    mergeSequences(slotBefore);
                                } else if (slot !== slotListEnd && !listEndReached) {
                                    moveSequenceBefore(slotsEnd, slotFirstInSequence, slot);
                                }
                            } else {
                                if (indexMax < slot.index && !listEndReached) {
                                    indexMax = slot.index;
                                } else {
                                    // Find the correct insertion point
                                    for (slotAfter = slotsStart.next; slotAfter.index < slot.index; slotAfter = slotAfter.next) {
                                        /*@empty*/
                                    }

                                    // Move the items individually, so move notifications are sent
                                    for (var slotMove = slotFirstInSequence; slotMove !== slotNext;) {
                                        var slotMoveNext = slotMove.next;
                                        slotRefresh = slotRefreshFromSlot(slotMove);
                                        moveSlot(slotMove, slotAfter, slotAfter.prev.index === slotMove.index - 1, slotAfter.index === slotMove.index + 1, slotRefresh && slotRefresh.locationJustDetermined);
                                        slotMove = slotMoveNext;
                                    }
                                }

                                // Store slotBefore here since the sequence might have just been moved
                                slotBefore = slotFirstInSequence.prev;

                                // See if this sequence should be merged with the previous one
                                if (slotBefore && slotBefore.index === slotFirstInSequence.index - 1) {
                                    mergeSequences(slotBefore);
                                }
                            }
                        }

                        if (slot === slotListEnd) {
                            listEndReached = true;
                        }

                        slot = slotNext;
                    }

                    indexUpdateDeferred = false;

                    // Now that all the sequences have been moved, merge any colliding slots
                    mergeSequencePairs(sequencePairsToMerge);

                    // Send countChanged notification
                    if (refreshCount !== undefined && refreshCount !== knownCount) {
                        changeCount(refreshCount);
                    }

                    finishNotifications();

                    // Before discarding the refresh slot list, see if any fetch requests can be completed by pretending each range
                    // of refresh slots is an incoming array of results.
                    var fetchResults = [];
                    for (i = 0; i < sequenceCountNew; i++) {
                        sequenceNew = sequencesNew[i];

                        var results = [];

                        slot = null;
                        offset = 0;

                        var slotOffset;

                        for (slotRefresh = sequenceNew.first; true; slotRefresh = slotRefresh.next, offset++) {
                            if (slotRefresh === refreshStart) {
                                results.push(startMarker);
                            } else if (slotRefresh === refreshEnd) {
                                results.push(endMarker);
                            } else {
                                results.push(slotRefresh.item);

                                if (!slot) {
                                    slot = slotFromSlotRefresh(slotRefresh);
                                    slotOffset = offset;
                                }
                            }

                            if (slotRefresh.lastInSequence) {
                                break;
                            }
                        }

                        if (slot) {
                            fetchResults.push({
                                slot: slot,
                                results: results,
                                offset: slotOffset
                            });
                        }
                    }

                    resetRefreshState();
                    refreshInProgress = false;

                    // Complete any promises for newly obtained items
                    callFetchCompleteCallbacks();

                    // Now process the 'extra' results from the refresh list
                    for (i = 0; i < fetchResults.length; i++) {
                        var fetchResult = fetchResults[i];
                        processResults(fetchResult.slot, fetchResult.results, fetchResult.offset, knownCount, fetchResult.slot.index);
                    }

                    if (refreshSignal) {
                        var signal = refreshSignal;

                        refreshSignal = null;

                        signal.complete();
                    }

                    // Finally, kick-start fetches for any remaining placeholders
                    postFetch();
                }

                // Edit Queue

                // Queues an edit and immediately "optimistically" apply it to the slots list, sending re-entrant notifications
                function queueEdit(applyEdit, editType, complete, error, keyUpdate, updateSlots, undo) {
                    var editQueueTail = editQueue.prev,
                        edit = {
                            prev: editQueueTail,
                            next: editQueue,
                            applyEdit: applyEdit,
                            editType: editType,
                            complete: complete,
                            error: error,
                            keyUpdate: keyUpdate
                        };
                    editQueueTail.next = edit;
                    editQueue.prev = edit;
                    editsQueued = true;

                    // If there's a refresh in progress, abandon it, but request that a new one be started once the edits complete
                    if (refreshRequested || refreshInProgress) {
                        currentRefreshID++;
                        refreshInProgress = false;
                        refreshRequested = true;
                    }

                    if (editQueue.next === edit) {
                        // Attempt the edit immediately, in case it completes synchronously
                        applyNextEdit();
                    }

                    // If the edit succeeded or is still pending, apply it to the slots (in the latter case, "optimistically")
                    if (!edit.failed) {
                        updateSlots();

                        // Supply the undo function now
                        edit.undo = undo;
                    }

                    if (!editsInProgress) {
                        completeEdits();
                    }
                }

                function dequeueEdit() {
                    firstEditInProgress = false;

                    var editNext = editQueue.next.next;

                    editQueue.next = editNext;
                    editNext.prev = editQueue;
                }

                // Undo all queued edits, starting with the most recent
                function discardEditQueue() {
                    while (editQueue.prev !== editQueue) {
                        var editLast = editQueue.prev;

                        if (editLast.error) {
                            editLast.error(new _ErrorFromName(EditError.canceled));
                        }

                        // Edits that haven't been applied to the slots yet don't need to be undone
                        if (editLast.undo && !refreshRequested) {
                            editLast.undo();
                        }

                        editQueue.prev = editLast.prev;
                    }
                    editQueue.next = editQueue;

                    editsInProgress = false;

                    completeEdits();
                }

                var EditType = {
                    insert: "insert",
                    change: "change",
                    move: "move",
                    remove: "remove"
                };

                function attemptEdit(edit) {
                    if (firstEditInProgress) {
                        return;
                    }

                    var reentrant = true;

                    function continueEdits() {
                        if (!waitForRefresh) {
                            if (reentrant) {
                                synchronousEdit = true;
                            } else {
                                applyNextEdit();
                            }
                        }
                    }

                    var keyUpdate = edit.keyUpdate;

                    function onEditComplete(item) {
                        if (item) {
                            var slot;
                            if (keyUpdate && keyUpdate.key !== item.key) {
                                var keyNew = item.key;
                                if (!edit.undo) {
                                    // If the edit is in the process of being queued, we can use the correct key when we update the
                                    // slots, so there's no need for a later update.
                                    keyUpdate.key = keyNew;
                                } else {
                                    slot = keyUpdate.slot;
                                    if (slot) {
                                        var keyOld = slot.key;
                                        if (keyOld) {
                                            delete keyMap[keyOld];
                                        }
                                        setSlotKey(slot, keyNew);
                                        slot.itemNew = item;
                                        if (slot.item) {
                                            changeSlot(slot);
                                            finishNotifications();
                                        } else {
                                            completeFetchPromises(slot);
                                        }
                                    }
                                }
                            } else if (edit.editType === EditType.change) {
                                slot.itemNew = item;

                                if (!reentrant) {
                                    changeSlotIfNecessary(slot);
                                }
                            }
                        }

                        dequeueEdit();

                        if (edit.complete) {
                            edit.complete(item);
                        }

                        continueEdits();
                    }

                    function onEditError(error) {
                        switch (error.Name) {
                            case EditError.noResponse:
                                // Report the failure to the client, but do not dequeue the edit
                                setStatus(DataSourceStatus.failure);
                                waitForRefresh = true;

                                firstEditInProgress = false;

                                // Don't report the error, as the edit will be attempted again on the next refresh
                                return;

                            case EditError.notPermitted:
                                break;

                            case EditError.noLongerMeaningful:
                                // Something has changed, so request a refresh
                                beginRefresh();
                                break;

                            default:
                                break;
                        }

                        // Discard all remaining edits, rather than try to determine which subsequent ones depend on this one
                        edit.failed = true;
                        dequeueEdit();

                        discardEditQueue();

                        if (edit.error) {
                            edit.error(error);
                        }

                        continueEdits();
                    }

                    if (listDataAdapter.beginEdits && !beginEditsCalled) {
                        beginEditsCalled = true;
                        listDataAdapter.beginEdits();
                    }

                    // Call the applyEdit function for the given edit, passing in our own wrapper of the error handler that the
                    // client passed in.
                    firstEditInProgress = true;
                    edit.applyEdit().then(onEditComplete, onEditError);
                    reentrant = false;
                }

                function applyNextEdit() {
                    // See if there are any outstanding edits, and try to process as many as possible synchronously
                    while (editQueue.next !== editQueue) {
                        synchronousEdit = false;
                        attemptEdit(editQueue.next);
                        if (!synchronousEdit) {
                            return;
                        }
                    }

                    // The queue emptied out synchronously (or was empty to begin with)
                    concludeEdits();
                }

                function completeEdits() {
                    updateIndices();

                    finishNotifications();

                    callFetchCompleteCallbacks();

                    if (editQueue.next === editQueue) {
                        concludeEdits();
                    }
                }

                // Once the edit queue has emptied, update state appropriately and resume normal operation
                function concludeEdits() {
                    editsQueued = false;

                    if (listDataAdapter.endEdits && beginEditsCalled && !editsInProgress) {
                        beginEditsCalled = false;
                        listDataAdapter.endEdits();
                    }

                    // See if there's a refresh that needs to begin
                    if (refreshRequested) {
                        refreshRequested = false;
                        beginRefresh();
                    } else {
                        // Otherwise, see if anything needs to be fetched
                        postFetch();
                    }
                }

                // Editing Operations

                function getSlotForEdit(key) {
                    validateKey(key);

                    return keyMap[key] || createSlotForKey(key);
                }

                function insertNewSlot(key, itemNew, slotInsertBefore, mergeWithPrev, mergeWithNext) {
                    // Create a new slot, but don't worry about its index, as indices will be updated during endEdits
                    var slot = createPrimarySlot();

                    insertAndMergeSlot(slot, slotInsertBefore, mergeWithPrev, mergeWithNext);
                    if (key) {
                        setSlotKey(slot, key);
                    }
                    slot.itemNew = itemNew;

                    updateNewIndices(slot, 1);

                    // If this isn't part of a batch of changes, set the slot index now so renderers can see it
                    if (!editsInProgress && !dataNotificationsInProgress) {
                        if (!slot.firstInSequence && typeof slot.prev.index === "number") {
                            setSlotIndex(slot, slot.prev.index + 1, indexMap);
                        } else if (!slot.lastInSequence && typeof slot.next.index === "number") {
                            setSlotIndex(slot, slot.next.index - 1, indexMap);
                        }
                    }

                    prepareSlotItem(slot);

                    // Send the notification after the insertion
                    sendInsertedNotification(slot);

                    return slot;
                }

                function insertItem(key, data, slotInsertBefore, append, applyEdit) {
                    var keyUpdate = { key: key };

                    return new Promise(function (complete, error) {
                        queueEdit(
                            applyEdit, EditType.insert, complete, error, keyUpdate,

                            // updateSlots
                            function () {
                                if (slotInsertBefore) {
                                    var itemNew = {
                                        key: keyUpdate.key,
                                        data: data
                                    };

                                    keyUpdate.slot = insertNewSlot(keyUpdate.key, itemNew, slotInsertBefore, append, !append);
                                }
                            },

                            // undo
                            function () {
                                var slot = keyUpdate.slot;

                                if (slot) {
                                    updateNewIndices(slot, -1);
                                    deleteSlot(slot, false);
                                }
                            }
                        );
                    });
                }

                function moveItem(slot, slotMoveBefore, append, applyEdit) {
                    return new Promise(function (complete, error) {
                        var mergeAdjacent,
                            slotNext,
                            firstInSequence,
                            lastInSequence;

                        queueEdit(
                            applyEdit, EditType.move, complete, error,

                            // keyUpdate
                            null,

                            // updateSlots
                            function () {
                                slotNext = slot.next;
                                firstInSequence = slot.firstInSequence;
                                lastInSequence = slot.lastInSequence;

                                var slotPrev = slot.prev;

                                mergeAdjacent = (typeof slot.index !== "number" && (firstInSequence || !slotPrev.item) && (lastInSequence || !slotNext.item));

                                updateNewIndices(slot, -1);
                                moveSlot(slot, slotMoveBefore, append, !append);
                                updateNewIndices(slot, 1);

                                if (mergeAdjacent) {
                                    splitSequence(slotPrev);

                                    if (!firstInSequence) {
                                        mergeSlotsBefore(slotPrev, slot);
                                    }
                                    if (!lastInSequence) {
                                        mergeSlotsAfter(slotNext, slot);
                                    }
                                }
                            },

                            // undo
                            function () {
                                if (!mergeAdjacent) {
                                    updateNewIndices(slot, -1);
                                    moveSlot(slot, slotNext, !firstInSequence, !lastInSequence);
                                    updateNewIndices(slot, 1);
                                } else {
                                    beginRefresh();
                                }
                            }
                        );
                    });
                }

                function ListDataNotificationHandler() {
                    /// <signature helpKeyword="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler">
                    /// <summary locid="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler">
                    /// An implementation of IListDataNotificationHandler that is passed to the
                    /// IListDataAdapter.setNotificationHandler method.
                    /// </summary>
                    /// </signature>

                    this.invalidateAll = function () {
                        /// <signature helpKeyword="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.invalidateAll">
                        /// <summary locid="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.invalidateAll">
                        /// Notifies the VirtualizedDataSource that some data has changed, without specifying which data. It might
                        /// be impractical for some data sources to call this method for any or all changes, so this call is optional.
                        /// But if a given data adapter never calls it, the application should periodically call
                        /// invalidateAll on the VirtualizedDataSource to refresh the data.
                        /// </summary>
                        /// <returns type="Promise" locid="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.invalidateAll_returnValue">
                        /// A Promise that completes when the data has been completely refreshed and all change notifications have
                        /// been sent.
                        /// </returns>
                        /// </signature>

                        if (knownCount === 0) {
                            this.reload();
                            return Promise.wrap();
                        }

                        return requestRefresh();
                    };

                    this.reload = function () {
                        /// <signature helpKeyword="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.reload">
                        /// <summary locid="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.reload">
                        /// Notifies the list data source that the list data has changed so much that it is better
                        /// to reload the data from scratch.
                        /// </summary>
                        /// </signature>

                        // Cancel all promises

                        if (getCountPromise) {
                            getCountPromise.cancel();
                        }

                        if (refreshSignal) {
                            refreshSignal.cancel();
                        }

                        for (var slot = slotsStart.next; slot !== slotsEnd; slot = slot.next) {
                            var fetchListeners = slot.fetchListeners;
                            for (var listenerID in fetchListeners) {
                                fetchListeners[listenerID].promise.cancel();
                            }
                            var directFetchListeners = slot.directFetchListeners;
                            for (var listenerID in directFetchListeners) {
                                directFetchListeners[listenerID].promise.cancel();
                            }
                        }

                        resetState();

                        forEachBindingRecord(function (bindingRecord) {
                            if (bindingRecord.notificationHandler) {
                                bindingRecord.notificationHandler.reload();
                            }
                        });
                    };

                    this.beginNotifications = function () {
                        /// <signature helpKeyword="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.beginNotifications">
                        /// <summary locid="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.beginNotifications">
                        /// Indicates the start of a notification batch.
                        /// Call it before a sequence of other notification calls to minimize the number of countChanged and
                        /// indexChanged notifications sent to the client of the VirtualizedDataSource. You must pair it with a call
                        /// to endNotifications, and pairs can't be nested.
                        /// </summary>
                        /// </signature>

                        dataNotificationsInProgress = true;
                    };

                    function completeNotification() {
                        if (!dataNotificationsInProgress) {
                            updateIndices();
                            finishNotifications();

                            callFetchCompleteCallbacks();
                        }
                    }

                    this.inserted = function (newItem, previousKey, nextKey, index) {
                        /// <signature helpKeyword="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.inserted">
                        /// <summary locid="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.inserted">
                        /// Raises a notification that an item was inserted.
                        /// </summary>
                        /// <param name="newItem" type="Object" locid="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.inserted_p:newItem">
                        /// The inserted item. It must have a key and a data property (it must implement the IItem interface).
                        /// </param>
                        /// <param name="previousKey" mayBeNull="true" type="String" locid="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.inserted_p:previousKey">
                        /// The key of the item before the insertion point, or null if the item was inserted at the start of the
                        /// list.  It can be null if you specified nextKey.
                        /// </param>
                        /// <param name="nextKey" mayBeNull="true" type="String" locid="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.inserted_p:nextKey">
                        /// The key of the item after the insertion point, or null if the item was inserted at the end of the list.
                        /// It can be null if you specified previousKey.
                        /// </param>
                        /// <param name="index" optional="true" type="Number" integer="true" locid="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.inserted_p:index">
                        /// The index of the inserted item.
                        /// </param>
                        /// </signature>

                        if (editsQueued) {
                            // We can't change the slots out from under any queued edits
                            beginRefresh();
                        } else {
                            var key = newItem.key,
                                slotPrev = keyMap[previousKey],
                                slotNext = keyMap[nextKey];

                            var havePreviousKey = typeof previousKey === "string",
                                haveNextKey = typeof nextKey === "string";

                            // Only one of previousKey, nextKey needs to be passed in
                            //
                            if (havePreviousKey) {
                                if (slotNext && !slotNext.firstInSequence) {
                                    slotPrev = slotNext.prev;
                                }
                            } else if (haveNextKey) {
                                if (slotPrev && !slotPrev.lastInSequence) {
                                    slotNext = slotPrev.next;
                                }
                            }

                            // If the VDS believes the list is empty but the data adapter believes the item has
                            // a adjacent item start a refresh.
                            //
                            if ((havePreviousKey || haveNextKey) && !(slotPrev || slotNext) && (slotsStart.next === slotListEnd)) {
                                beginRefresh();
                                return;
                            }

                            // If this key is known, something has changed, start a refresh.
                            //
                            if (keyMap[key]) {
                                beginRefresh();
                                return;
                            }

                            // If the slots aren't adjacent or are thought to be distinct sequences by the
                            //  VDS something has changed so start a refresh.
                            //
                            if (slotPrev && slotNext) {
                                if (slotPrev.next !== slotNext || slotPrev.lastInSequence || slotNext.firstInSequence) {
                                    beginRefresh();
                                    return;
                                }
                            }

                            // If one of the adjacent keys or indicies has only just been requested - rare,
                            //  and easier to deal with in a refresh.
                            //
                            if ((slotPrev && (slotPrev.keyRequested || slotPrev.indexRequested)) ||
                                (slotNext && (slotNext.keyRequested || slotNext.indexRequested))) {
                                beginRefresh();
                                return;
                            }

                            if (slotPrev || slotNext) {
                                insertNewSlot(key, newItem, (slotNext ? slotNext : slotPrev.next), !!slotPrev, !!slotNext);
                            } else if (slotsStart.next === slotListEnd) {
                                insertNewSlot(key, newItem, slotsStart.next, true, true);
                            } else if (index !== undefined) {
                                updateNewIndicesFromIndex(index, 1);
                            } else {
                                // We could not find a previous or next slot and an index was not provided, start a refresh
                                //
                                beginRefresh();
                                return;
                            }

                            completeNotification();
                        }
                    };

                    this.changed = function (item) {
                        /// <signature helpKeyword="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.changed">
                        /// <summary locid="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.changed">
                        /// Raises a notification that an item changed.
                        /// </summary>
                        /// <param name="item" type="Object" locid="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.changed_p:item">
                        /// An IItem that represents the item that changed.
                        /// </param>
                        /// </signature>

                        if (editsQueued) {
                            // We can't change the slots out from under any queued edits
                            beginRefresh();
                        } else {
                            var key = item.key,
                                slot = keyMap[key];

                            if (slot) {
                                if (slot.keyRequested) {
                                    // The key has only just been requested - rare, and easier to deal with in a refresh
                                    beginRefresh();
                                } else {
                                    slot.itemNew = item;

                                    if (slot.item) {
                                        changeSlot(slot);

                                        completeNotification();
                                    }
                                }
                            }
                        }
                    };

                    this.moved = function (item, previousKey, nextKey, oldIndex, newIndex) {
                        /// <signature helpKeyword="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.moved">
                        /// <summary locid="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.moved">
                        /// Raises a notfication that an item was moved.
                        /// </summary>
                        /// <param name="item" type="Object" locid="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.moved_p:item">
                        /// The item that was moved.
                        /// </param>
                        /// <param name="previousKey" mayBeNull="true" type="String" locid="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.moved_p:previousKey">
                        /// The key of the item before the insertion point, or null if the item was moved to the beginning of the list.
                        /// It can be null if you specified nextKey.
                        /// </param>
                        /// <param name="nextKey" mayBeNull="true" type="String" locid="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.moved_p:nextKey">
                        /// The key of the item after the insertion point, or null if the item was moved to the end of the list.
                        /// It can be null if you specified previousKey.
                        /// </param>
                        /// <param name="oldIndex" optional="true" type="Number" integer="true" locid="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.moved_p:oldIndex">
                        /// The index of the item before it was moved.
                        /// </param>
                        /// <param name="newIndex" optional="true" type="Number" integer="true" locid="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.moved_p:newIndex">
                        /// The index of the item after it was moved.
                        /// </param>
                        /// </signature>

                        if (editsQueued) {
                            // We can't change the slots out from under any queued edits
                            beginRefresh();
                        } else {
                            var key = item.key,
                                slot = keyMap[key],
                                slotPrev = keyMap[previousKey],
                                slotNext = keyMap[nextKey];

                            if ((slot && slot.keyRequested) || (slotPrev && slotPrev.keyRequested) || (slotNext && slotNext.keyRequested)) {
                                // One of the keys has only just been requested - rare, and easier to deal with in a refresh
                                beginRefresh();
                            } else if (slot) {
                                if (slotPrev && slotNext && (slotPrev.next !== slotNext || slotPrev.lastInSequence || slotNext.firstInSequence)) {
                                    // Something has changed, start a refresh
                                    beginRefresh();
                                } else if (!slotPrev && !slotNext) {
                                    // If we can't tell where the item moved to, treat this like a removal
                                    updateNewIndices(slot, -1);
                                    deleteSlot(slot, false);

                                    if (oldIndex !== undefined) {
                                        if (oldIndex < newIndex) {
                                            newIndex--;
                                        }

                                        updateNewIndicesFromIndex(newIndex, 1);
                                    }

                                    completeNotification();
                                } else {
                                    updateNewIndices(slot, -1);
                                    moveSlot(slot, (slotNext ? slotNext : slotPrev.next), !!slotPrev, !!slotNext);
                                    updateNewIndices(slot, 1);

                                    completeNotification();
                                }
                            } else if (slotPrev || slotNext) {
                                // If previousKey or nextKey is known, but key isn't, treat this like an insertion.

                                if (oldIndex !== undefined) {
                                    updateNewIndicesFromIndex(oldIndex, -1);

                                    if (oldIndex < newIndex) {
                                        newIndex--;
                                    }
                                }

                                this.inserted(item, previousKey, nextKey, newIndex);
                            } else if (oldIndex !== undefined) {
                                updateNewIndicesFromIndex(oldIndex, -1);

                                if (oldIndex < newIndex) {
                                    newIndex--;
                                }

                                updateNewIndicesFromIndex(newIndex, 1);

                                completeNotification();
                            }
                        }
                    };

                    this.removed = function (key, index) {
                        /// <signature helpKeyword="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.removed">
                        /// <summary locid="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.removed">
                        /// Raises a notification that an item was removed.
                        /// </summary>
                        /// <param name="key" mayBeNull="true" type="String" locid="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.removed_p:key">
                        /// The key of the item that was removed.
                        /// </param>
                        /// <param name="index" optional="true" type="Number" integer="true" locid="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.removed_p:index">
                        /// The index of the item that was removed.
                        /// </param>
                        /// </signature>

                        if (editsQueued) {
                            // We can't change the slots out from under any queued edits
                            beginRefresh();
                        } else {
                            var slot;

                            if (typeof key === "string") {
                                slot = keyMap[key];
                            } else {
                                slot = indexMap[index];
                            }

                            if (slot) {
                                if (slot.keyRequested) {
                                    // The key has only just been requested - rare, and easier to deal with in a refresh
                                    beginRefresh();
                                } else {
                                    updateNewIndices(slot, -1);
                                    deleteSlot(slot, false);

                                    completeNotification();
                                }
                            } else if (index !== undefined) {
                                updateNewIndicesFromIndex(index, -1);
                                completeNotification();
                            }
                        }
                    };

                    this.endNotifications = function () {
                        /// <signature helpKeyword="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.endNotifications">
                        /// <summary locid="WinJS.UI.VirtualizedDataSource.ListDataNotificationHandler.endNotifications">
                        /// Concludes a sequence of notifications that began with a call to beginNotifications.
                        /// </summary>
                        /// </signature>

                        dataNotificationsInProgress = false;
                        completeNotification();
                    };

                } // ListDataNotificationHandler

                function resetState() {
                    setStatus(DataSourceStatus.ready);

                    // Track count promises
                    getCountPromise = null;

                    // Track whether listDataAdapter.endEdits needs to be called
                    beginEditsCalled = false;

                    // Track whether finishNotifications should be called after each edit
                    editsInProgress = false;

                    // Track whether the first queued edit should be attempted
                    firstEditInProgress = false;

                    // Queue of edis that have yet to be completed
                    editQueue = {};
                    editQueue.next = editQueue;
                    editQueue.prev = editQueue;

                    // Track whether there are currently edits queued
                    editsQueued = false;

                    // If an edit has returned noResponse, the edit queue will be reapplied when the next refresh is requested
                    waitForRefresh = false;

                    // Change to count while multiple edits are taking place
                    countDelta = 0;

                    // True while the indices are temporarily in a bad state due to multiple edits
                    indexUpdateDeferred = false;

                    // Next temporary key to use
                    nextTempKey = 0;

                    // Set of fetches for which results have not yet arrived
                    fetchesInProgress = {};

                    // Queue of complete callbacks for fetches
                    fetchCompleteCallbacks = [];

                    // Tracks the count returned explicitly or implicitly by the data adapter
                    knownCount = CountResult.unknown;

                    // Sentinel objects for list of slots
                    // Give the start sentinel an index so we can always use predecessor + 1.
                    slotsStart = {
                        firstInSequence: true,
                        lastInSequence: true,
                        index: -1
                    };
                    slotListEnd = {
                        firstInSequence: true,
                        lastInSequence: true
                    };
                    slotsEnd = {
                        firstInSequence: true,
                        lastInSequence: true
                    };
                    slotsStart.next = slotListEnd;
                    slotListEnd.prev = slotsStart;
                    slotListEnd.next = slotsEnd;
                    slotsEnd.prev = slotListEnd;

                    // Map of request IDs to slots
                    handleMap = {};

                    // Map of keys to slots
                    keyMap = {};

                    // Map of indices to slots
                    indexMap = {};
                    indexMap[-1] = slotsStart;

                    // Count of slots that have been released but not deleted
                    releasedSlots = 0;

                    lastSlotReleased = null;

                    // At most one call to reduce the number of refresh slots should be posted at any given time
                    reduceReleasedSlotCountPosted = false;

                    // Multiple refresh requests are coalesced
                    refreshRequested = false;

                    // Requests do not cause fetches while a refresh is in progress
                    refreshInProgress = false;

                    // Refresh requests yield the same promise until a refresh completes
                    refreshSignal = null;
                }

                // Construction

                // Process creation parameters
                if (!listDataAdapter) {
                    throw new _ErrorFromName("WinJS.UI.ListDataSource.ListDataAdapterIsInvalid", strings.listDataAdapterIsInvalid);
                }

                // Minimum number of released slots to retain
                cacheSize = (listDataAdapter.compareByIdentity ? 0 : 200);

                if (options) {
                    if (typeof options.cacheSize === "number") {
                        cacheSize = options.cacheSize;
                    }
                }

                // Cached listDataNotificationHandler initially undefined
                if (listDataAdapter.setNotificationHandler) {
                    listDataNotificationHandler = new ListDataNotificationHandler();

                    listDataAdapter.setNotificationHandler(listDataNotificationHandler);
                }

                // Current status
                status = DataSourceStatus.ready;

                // Track whether a change to the status has been posted already
                statusChangePosted = false;

                // Map of bindingIDs to binding records
                bindingMap = {};

                // ID to assign to the next ListBinding, incremented each time one is created
                nextListBindingID = 0;

                // ID assigned to a slot, incremented each time one is created - start with 1 so "if (handle)" tests are valid
                nextHandle = 1;

                // ID assigned to a fetch listener, incremented each time one is created
                nextListenerID = 0;

                // ID of the refresh in progress, incremented each time a new refresh is started
                currentRefreshID = 0;

                // Track whether fetchItemsForAllSlots has been posted already
                fetchesPosted = false;

                // ID of a fetch, incremented each time a new fetch is initiated - start with 1 so "if (fetchID)" tests are valid
                nextFetchID = 1;

                // Sentinel objects for results arrays
                startMarker = {};
                endMarker = {};

                resetState();

                // Public methods

                this.createListBinding = function (notificationHandler) {
                    /// <signature helpKeyword="WinJS.UI.IListDataSource.createListBinding">
                    /// <summary locid="WinJS.UI.IListDataSource.createListBinding">
                    /// Creates an IListBinding object that allows a client to read from the list and receive notifications for
                    /// changes that affect those portions of the list that the client already read.
                    /// </summary>
                    /// <param name="notificationHandler" optional="true" locid="WinJS.UI.IListDataSource.createListBinding_p:notificationHandler">
                    /// An object that implements the IListNotificationHandler interface.  If you omit this parameter,
                    /// change notifications won't be available.
                    /// </param>
                    /// <returns type="IListBinding" locid="WinJS.UI.IListDataSource.createListBinding_returnValue">
                    /// An object that implements the IListBinding interface.
                    /// </returns>
                    /// </signature>

                    var listBindingID = (nextListBindingID++).toString(),
                        slotCurrent = null,
                        released = false;

                    function retainSlotForCursor(slot) {
                        if (slot) {
                            slot.cursorCount++;
                        }
                    }

                    function releaseSlotForCursor(slot) {
                        if (slot) {
                            if (--slot.cursorCount === 0) {
                                releaseSlotIfUnrequested(slot);
                            }
                        }
                    }

                    function moveCursor(slot) {
                        // Retain the new slot first just in case it's the same slot
                        retainSlotForCursor(slot);
                        releaseSlotForCursor(slotCurrent);
                        slotCurrent = slot;
                    }

                    function adjustCurrentSlot(slot, slotNew) {
                        if (slot === slotCurrent) {
                            if (!slotNew) {
                                slotNew = (
                                    !slotCurrent || slotCurrent.lastInSequence || slotCurrent.next === slotListEnd ?
                                        null :
                                        slotCurrent.next
                                );
                            }
                            moveCursor(slotNew);
                        }
                    }

                    function releaseSlotFromListBinding(slot) {
                        var bindingMap = slot.bindingMap,
                            bindingHandle = bindingMap[listBindingID].handle;

                        delete slot.bindingMap[listBindingID];

                        // See if there are any listBindings left in the map
                        var releaseBindingMap = true,
                            releaseHandle = true;
                        for (var listBindingID2 in bindingMap) {
                            releaseBindingMap = false;
                            if (bindingHandle && bindingMap[listBindingID2].handle === bindingHandle) {
                                releaseHandle = false;
                                break;
                            }
                        }

                        if (bindingHandle && releaseHandle) {
                            delete handleMap[bindingHandle];
                        }
                        if (releaseBindingMap) {
                            slot.bindingMap = null;
                            releaseSlotIfUnrequested(slot);
                        }
                    }

                    function retainItem(slot, listenerID) {
                        if (!slot.bindingMap) {
                            slot.bindingMap = {};
                        }

                        var slotBinding = slot.bindingMap[listBindingID];
                        if (slotBinding) {
                            slotBinding.count++;
                        } else {
                            slot.bindingMap[listBindingID] = {
                                bindingRecord: bindingMap[listBindingID],
                                count: 1
                            };
                        }

                        if (slot.fetchListeners) {
                            var listener = slot.fetchListeners[listenerID];
                            if (listener) {
                                listener.retained = true;
                            }
                        }
                    }

                    function releaseItem(handle) {
                        var slot = handleMap[handle];

                        if (slot) {
                            var slotBinding = slot.bindingMap[listBindingID];
                            if (--slotBinding.count === 0) {
                                var fetchListeners = slot.fetchListeners;
                                for (var listenerID in fetchListeners) {
                                    var listener = fetchListeners[listenerID];
                                    if (listener.listBindingID === listBindingID) {
                                        listener.retained = false;
                                    }
                                }

                                releaseSlotFromListBinding(slot);
                            }
                        }
                    }

                    function itemPromiseFromKnownSlot(slot) {
                        var handle = handleForBinding(slot, listBindingID),
                            listenerID = (nextListenerID++).toString();

                        var itemPromise = createFetchPromise(slot, "fetchListeners", listenerID, listBindingID,
                            function (complete, item) {
                                complete(itemForBinding(item, handle));
                            }
                        );

                        defineCommonItemProperties(itemPromise, slot, handle);

                        // Only implement retain and release methods if a notification handler has been supplied
                        if (notificationHandler) {
                            itemPromise.retain = function () {
                                listBinding._retainItem(slot, listenerID);
                                return itemPromise;
                            };

                            itemPromise.release = function () {
                                listBinding._releaseItem(handle);
                            };
                        }

                        return itemPromise;
                    }

                    bindingMap[listBindingID] = {
                        notificationHandler: notificationHandler,
                        notificationsSent: false,
                        adjustCurrentSlot: adjustCurrentSlot,
                        itemPromiseFromKnownSlot: itemPromiseFromKnownSlot,
                    };

                    function itemPromiseFromSlot(slot) {
                        var itemPromise;

                        if (!released && slot) {
                            itemPromise = itemPromiseFromKnownSlot(slot);
                        } else {
                            // Return a complete promise for a non-existent slot
                            if (released) {
                                itemPromise = new Promise(function () { });
                                itemPromise.cancel();
                            } else {
                                itemPromise = Promise.wrap(null);
                            }
                            defineHandleProperty(itemPromise, null);
                            // Only implement retain and release methods if a notification handler has been supplied
                            if (notificationHandler) {
                                itemPromise.retain = function () { return itemPromise; };
                                itemPromise.release = function () { };
                            }
                        }

                        moveCursor(slot);

                        return itemPromise;
                    }

                    /// <signature helpKeyword="WinJS.UI.IListBinding">
                    /// <summary locid="WinJS.UI.IListBinding">
                    /// An interface that enables a client to read from the list and receive notifications for changes that affect
                    /// those portions of the list that the client already read.  IListBinding can also enumerate through lists
                    /// that can change at any time.
                    /// </summary>
                    /// </signature>
                    var listBinding = {
                        _retainItem: function (slot, listenerID) {
                            retainItem(slot, listenerID);
                        },

                        _releaseItem: function (handle) {
                            releaseItem(handle);
                        },

                        jumpToItem: function (item) {
                            /// <signature helpKeyword="WinJS.UI.IListBinding.jumpToItem">
                            /// <summary locid="WinJS.UI.IListBinding.jumpToItem">
                            /// Makes the specified item the current item.
                            /// </summary>
                            /// <param name="item" type="Object" locid="WinJS.UI.IListBinding.jumpToItem_p:item">
                            /// The IItem or IItemPromise to make the current item.
                            /// </param>
                            /// <returns type="IItemPromise" locid="WinJS.UI.IListBinding.jumpToItem_returnValue">
                            /// An object that implements the IItemPromise interface and serves as a promise for the specified item.  If
                            /// the specified item is not in the list, the promise completes with a value of null.
                            /// </returns>
                            /// </signature>

                            return itemPromiseFromSlot(item ? handleMap[item.handle] : null);
                        },

                        current: function () {
                            /// <signature helpKeyword="WinJS.UI.IListBinding.current">
                            /// <summary locid="WinJS.UI.IListBinding.current">
                            /// Retrieves the current item.
                            /// </summary>
                            /// <returns type="IItemPromise" locid="WinJS.UI.IListBinding.current_returnValue">
                            /// An object that implements the IItemPromise interface and serves as a promise for the current item.
                            /// If the cursor has moved past the start or end of the list, the promise completes with a value
                            /// of null.  If the current item has been deleted or moved, the promise returns an error.
                            /// </returns>
                            /// </signature>

                            return itemPromiseFromSlot(slotCurrent);
                        },

                        previous: function () {
                            /// <signature helpKeyword="WinJS.UI.IListBinding.previous">
                            /// <summary locid="WinJS.UI.IListBinding.previous">
                            /// Retrieves the item before the current item and makes it the current item.
                            /// </summary>
                            /// <returns type="IItemPromise" locid="WinJS.UI.IListBinding.previous_returnValue">
                            /// An object that implements the IItemPromise interface and serves as a promise for the previous item.
                            /// If the cursor moves past the start of the list, the promise completes with a value of null.
                            /// </returns>
                            /// </signature>

                            return itemPromiseFromSlot(slotCurrent ? requestSlotBefore(slotCurrent) : null);
                        },

                        next: function () {
                            /// <signature helpKeyword="WinJS.UI.IListBinding.next">
                            /// <summary locid="WinJS.UI.IListBinding.next">
                            /// Retrieves the item after the current item and makes it the current item.
                            /// </summary>
                            /// <returns type="IItemPromise" locid="WinJS.UI.IListBinding.next_returnValue">
                            /// An object that implements the IItemPromise interface and serves as a promise for the next item.  If
                            /// the cursor moves past the end of the list, the promise completes with a value of null.
                            /// </returns>
                            /// </signature>

                            return itemPromiseFromSlot(slotCurrent ? requestSlotAfter(slotCurrent) : null);
                        },

                        releaseItem: function (item) {
                            /// <signature helpKeyword="WinJS.UI.IListBinding.releaseItem">
                            /// <summary locid="WinJS.UI.IListBinding.releaseItem">
                            /// Creates a request to stop change notfications for the specified item. The item is released only when the
                            /// number of release calls matches the number of IItemPromise.retain calls. The number of release calls cannot
                            /// exceed the number of retain calls. This method is present only if you passed an IListNotificationHandler
                            /// to IListDataSource.createListBinding when it created this IListBinding.
                            /// </summary>
                            /// <param name="item" type="Object" locid="WinJS.UI.IListBinding.releaseItem_p:item">
                            /// The IItem or IItemPromise to release.
                            /// </param>
                            /// </signature>

                            this._releaseItem(item.handle);
                        },

                        release: function () {
                            /// <signature helpKeyword="WinJS.UI.IListBinding.release">
                            /// <summary locid="WinJS.UI.IListBinding.release">
                            /// Releases resources, stops notifications, and cancels outstanding promises
                            /// for all tracked items that this IListBinding returned.
                            /// </summary>
                            /// </signature>

                            released = true;

                            releaseSlotForCursor(slotCurrent);
                            slotCurrent = null;

                            for (var slot = slotsStart.next; slot !== slotsEnd;) {
                                var slotNext = slot.next;

                                var fetchListeners = slot.fetchListeners;
                                for (var listenerID in fetchListeners) {
                                    var listener = fetchListeners[listenerID];
                                    if (listener.listBindingID === listBindingID) {
                                        listener.promise.cancel();
                                        delete fetchListeners[listenerID];
                                    }
                                }

                                if (slot.bindingMap && slot.bindingMap[listBindingID]) {
                                    releaseSlotFromListBinding(slot);
                                }

                                slot = slotNext;
                            }

                            delete bindingMap[listBindingID];
                        }
                    };

                    // Only implement each navigation method if the data adapter implements certain methods

                    if (itemsFromStart || itemsFromIndex) {
                        listBinding.first = function () {
                            /// <signature helpKeyword="WinJS.UI.IListBinding.first">
                            /// <summary locid="WinJS.UI.IListBinding.first">
                            /// Retrieves the first item in the list and makes it the current item.
                            /// </summary>
                            /// <returns type="IItemPromise" locid="WinJS.UI.IListBinding.first_returnValue">
                            /// An IItemPromise that serves as a promise for the requested item.
                            /// If the list is empty, the Promise completes with a value of null.
                            /// </returns>
                            /// </signature>

                            return itemPromiseFromSlot(requestSlotAfter(slotsStart));
                        };
                    }

                    if (itemsFromEnd) {
                        listBinding.last = function () {
                            /// <signature helpKeyword="WinJS.UI.IListBinding.last">
                            /// <summary locid="WinJS.UI.IListBinding.last">
                            /// Retrieves the last item in the list and makes it the current item.
                            /// </summary>
                            /// <returns type="IItemPromise" locid="WinJS.UI.IListBinding.last_returnValue">
                            /// An IItemPromise that serves as a promise for the requested item.
                            /// If the list is empty, the Promise completes with a value of null.
                            /// </returns>
                            /// </signature>

                            return itemPromiseFromSlot(requestSlotBefore(slotListEnd));
                        };
                    }

                    if (itemsFromKey) {
                        listBinding.fromKey = function (key, hints) {
                            /// <signature helpKeyword="WinJS.UI.IListBinding.fromKey">
                            /// <summary locid="WinJS.UI.IListBinding.fromKey">
                            /// Retrieves the item with the specified key and makes it the current item.
                            /// </summary>
                            /// <param name="key" type="String" locid="WinJS.UI.IListBinding.fromKey_p:key">
                            /// The key of the requested item. It must be a non-empty string.
                            /// </param>
                            /// <param name="hints" locid="WinJS.UI.IListBinding.fromKey_p:hints">
                            /// Domain-specific hints to the IListDataAdapter
                            /// about the location of the item to improve retrieval time.
                            /// </param>
                            /// <returns type="IItemPromise" locid="WinJS.UI.IListBinding.fromKey_returnValue">
                            /// An IItemPromise that serves as a promise for the requested item.
                            /// If the list doesn't contain an item with the specified key, the Promise completes with a value of null.
                            /// </returns>
                            /// </signature>

                            return itemPromiseFromSlot(slotFromKey(key, hints));
                        };
                    }

                    if (itemsFromIndex || (itemsFromStart && itemsFromKey)) {
                        listBinding.fromIndex = function (index) {
                            /// <signature helpKeyword="WinJS.UI.IListBinding.fromIndex">
                            /// <summary locid="WinJS.UI.IListBinding.fromIndex">
                            /// Retrieves the item with the specified index and makes it the current item.
                            /// </summary>
                            /// <param name="index" type="Nunmber" integer="true" locid="WinJS.UI.IListBinding.fromIndex_p:index">
                            /// A value greater than or equal to 0 that is the index of the item to retrieve.
                            /// </param>
                            /// <returns type="IItemPromise" locid="WinJS.UI.IListBinding.fromIndex_returnValue">
                            /// An IItemPromise that serves as a promise for the requested item.
                            /// If the list doesn't contain an item with the specified index, the IItemPromise completes with a value of null.
                            /// </returns>
                            /// </signature>

                            return itemPromiseFromSlot(slotFromIndex(index));
                        };
                    }

                    if (itemsFromDescription) {
                        listBinding.fromDescription = function (description) {
                            /// <signature helpKeyword="WinJS.UI.IListBinding.fromDescription">
                            /// <summary locid="WinJS.UI.IListBinding.fromDescription">
                            /// Retrieves the item with the specified description and makes it the current item.
                            /// </summary>
                            /// <param name="description" locid="WinJS.UI.IListDataSource.fromDescription_p:description">
                            /// The domain-specific description of the requested item, to be interpreted by the list data adapter.
                            /// </param>
                            /// <returns type="Promise" locid="WinJS.UI.IListDataSource.fromDescription_returnValue">
                            /// A Promise for the requested item. If the list doesn't contain an item with the specified description,
                            /// the IItemPromise completes with a value of null.
                            /// </returns>
                            /// </signature>

                            return itemPromiseFromSlot(slotFromDescription(description));
                        };
                    }

                    return listBinding;
                };

                this.invalidateAll = function () {
                    /// <signature helpKeyword="WinJS.UI.IListDataSource.invalidateAll">
                    /// <summary locid="WinJS.UI.IListDataSource.invalidateAll">
                    /// Makes the data source refresh its cached items by re-requesting them from the data adapter.
                    /// The data source generates notifications if the data has changed.
                    /// </summary>
                    /// <returns type="Promise" locid="WinJS.UI.IListDataSource.invalidateAll_returnValue">
                    /// A Promise that completes when the data has been completely refreshed and all change notifications have been
                    /// sent.
                    /// </returns>
                    /// </signature>

                    return requestRefresh();
                };

                // Create a helper which issues new promises for the result of the input promise
                //  but have their cancelations ref-counted so that any given consumer canceling
                //  their promise doesn't result in the incoming promise being canceled unless
                //  all consumers are no longer interested in the result.
                //
                var countedCancelation = function (incomingPromise, dataSource) {
                    var signal = new _Signal();
                    incomingPromise.then(
                        function (v) { signal.complete(v); },
                        function (e) { signal.error(e); }
                    );
                    var resultPromise = signal.promise.then(null, function (e) {
                        if (e.name === "WinJS.UI.VirtualizedDataSource.resetCount") {
                            getCountPromise = null;
                            return incomingPromise = dataSource.getCount();
                        }
                        return Promise.wrapError(e);
                    });
                    var count = 0;
                    var currentGetCountPromise = {
                        get: function () {
                            count++;
                            return new Promise(
                                function (c, e) { resultPromise.then(c, e); },
                                function () {
                                    if (--count === 0) {
                                        // when the count reaches zero cancel the incoming promise
                                        signal.promise.cancel();
                                        incomingPromise.cancel();
                                        if (currentGetCountPromise === getCountPromise) {
                                            getCountPromise = null;
                                        }
                                    }
                                }
                            );
                        },
                        reset: function () {
                            signal.error(new _ErrorFromName("WinJS.UI.VirtualizedDataSource.resetCount"));
                        },
                        cancel: function () {
                            // if explicitly asked to cancel the incoming promise
                            signal.promise.cancel();
                            incomingPromise.cancel();
                            if (currentGetCountPromise === getCountPromise) {
                                getCountPromise = null;
                            }
                        }
                    };
                    return currentGetCountPromise;
                };

                this.getCount = function () {
                    /// <signature helpKeyword="WinJS.UI.IListDataSource.getCount">
                    /// <summary locid="WinJS.UI.IListDataSource.getCount">
                    /// Retrieves the number of items in the data source.
                    /// </summary>
                    /// </signature>

                    if (listDataAdapter.getCount) {
                        // Always do a fetch, even if there is a cached result
                        //
                        var that = this;
                        return Promise.wrap().then(function () {
                            if (editsInProgress || editsQueued) {
                                return knownCount;
                            }

                            var requestPromise;

                            if (!getCountPromise) {

                                var relatedGetCountPromise;

                                // Make a request for the count
                                //
                                requestPromise = listDataAdapter.getCount();
                                var synchronous;
                                requestPromise.then(
                                    function () {
                                        if (getCountPromise === relatedGetCountPromise) {
                                            getCountPromise = null;
                                        }
                                        synchronous = true;
                                    },
                                    function () {
                                        if (getCountPromise === relatedGetCountPromise) {
                                            getCountPromise = null;
                                        }
                                        synchronous = true;
                                    }
                                );

                                // Every time we make a new request for the count we can consider the
                                //  countDelta to be invalidated
                                //
                                countDelta = 0;

                                // Wrap the result in a cancelation counter which will block cancelation
                                //  of the outstanding promise unless all consumers cancel.
                                //
                                if (!synchronous) {
                                    relatedGetCountPromise = getCountPromise = countedCancelation(requestPromise, that);
                                }
                            }

                            return getCountPromise ? getCountPromise.get() : requestPromise;

                        }).then(function (count) {
                            if (!isNonNegativeInteger(count) && count !== undefined) {
                                throw new _ErrorFromName("WinJS.UI.ListDataSource.InvalidRequestedCountReturned", strings.invalidRequestedCountReturned);
                            }

                            if (count !== knownCount) {
                                if (knownCount === CountResult.unknown) {
                                    knownCount = count;
                                } else {
                                    changeCount(count);
                                    finishNotifications();
                                }
                            }

                            if (count === 0) {
                                if (slotsStart.next !== slotListEnd || slotListEnd.next !== slotsEnd) {
                                    // A contradiction has been found
                                    beginRefresh();
                                } else if (slotsStart.lastInSequence) {
                                    // Now we know the list is empty
                                    mergeSequences(slotsStart);
                                    slotListEnd.index = 0;
                                }
                            }

                            return count;
                        }).then(null, function (error) {
                            if (error.name === _UI.CountError.noResponse) {
                                // Report the failure, but still report last known count
                                setStatus(DataSourceStatus.failure);
                                return knownCount;
                            }
                            return Promise.wrapError(error);
                        });
                    } else {
                        // If the data adapter doesn't support the count method, return the VirtualizedDataSource's
                        //  reckoning of the count.
                        return Promise.wrap(knownCount);
                    }
                };

                if (itemsFromKey) {
                    this.itemFromKey = function (key, hints) {
                        /// <signature helpKeyword="WinJS.UI.IListDataSource.itemFromKey">
                        /// <summary locid="WinJS.UI.IListDataSource.itemFromKey">
                        /// Retrieves the item with the specified key.
                        /// </summary>
                        /// <param name="key" type="String" locid="WinJS.UI.IListDataSource.itemFromKey_p:key">
                        /// The key of the requested item. It must be a non-empty string.
                        /// </param>
                        /// <param name="hints" locid="WinJS.UI.IListDataSource.itemFromKey_p:hints">
                        /// Domain-specific hints to IListDataAdapter about the location of the item
                        /// to improve the retrieval time.
                        /// </param>
                        /// <returns type="Promise" locid="WinJS.UI.IListDataSource.itemFromKey_returnValue">
                        /// A Promise for the requested item. If the list doesn't contain an item with the specified key,
                        /// the Promise completes with a value of null.
                        /// </returns>
                        /// </signature>

                        return itemDirectlyFromSlot(slotFromKey(key, hints));
                    };
                }

                if (itemsFromIndex || (itemsFromStart && itemsFromKey)) {
                    this.itemFromIndex = function (index) {
                        /// <signature helpKeyword="WinJS.UI.IListDataSource.itemFromIndex">
                        /// <summary locid="WinJS.UI.IListDataSource.itemFromIndex">
                        /// Retrieves the item at the specified index.
                        /// </summary>
                        /// <param name="index" type="Number" integer="true" locid="WinJS.UI.IListDataSource.itemFromIndex_p:index">
                        /// A value greater than or equal to zero that is the index of the requested item.
                        /// </param>
                        /// <returns type="Promise" locid="WinJS.UI.IListDataSource.itemFromIndex_returnValue">
                        /// A Promise for the requested item. If the list doesn't contain an item with the specified index,
                        /// the Promise completes with a value of null.
                        /// </returns>
                        /// </signature>

                        return itemDirectlyFromSlot(slotFromIndex(index));
                    };
                }

                if (itemsFromDescription) {
                    this.itemFromDescription = function (description) {
                        /// <signature helpKeyword="WinJS.UI.IListDataSource.itemFromDescription">
                        /// <summary locid="WinJS.UI.IListDataSource.itemFromDescription">
                        /// Retrieves the item with the specified description.
                        /// </summary>
                        /// <param name="description" locid="WinJS.UI.IListDataSource.itemFromDescription_p:description">
                        /// Domain-specific info that describes the item to retrieve, to be interpreted by the IListDataAdapter,
                        /// </param>
                        /// <returns type="Promise" locid="WinJS.UI.IListDataSource.itemFromDescription_returnValue">
                        /// A Promise for the requested item. If the list doesn't contain an item with the specified description,
                        /// the Promise completes with a value of null.
                        /// </returns>
                        /// </signature>

                        return itemDirectlyFromSlot(slotFromDescription(description));
                    };
                }

                this.beginEdits = function () {
                    /// <signature helpKeyword="WinJS.UI.IListDataSource.beginEdits">
                    /// <summary locid="WinJS.UI.IListDataSource.beginEdits">
                    /// Notifies the data source that a sequence of edits is about to begin.  The data source calls
                    /// IListNotificationHandler.beginNotifications and endNotifications each one time for a sequence of edits.
                    /// </summary>
                    /// </signature>

                    editsInProgress = true;
                };

                // Only implement each editing method if the data adapter implements the corresponding ListDataAdapter method

                if (listDataAdapter.insertAtStart) {
                    this.insertAtStart = function (key, data) {
                        /// <signature helpKeyword="WinJS.UI.IListDataSource.insertAtStart">
                        /// <summary locid="WinJS.UI.IListDataSource.insertAtStart">
                        /// Adds an item to the beginning of the data source.
                        /// </summary>
                        /// <param name="key" mayBeNull="true" type="String" locid="WinJS.UI.IListDataSource.insertAtStart_p:key">
                        /// The key of the item to insert, if known; otherwise, null.
                        /// </param>
                        /// <param name="data" locid="WinJS.UI.IListDataSource.insertAtStart_p:data">
                        /// The data for the item to add.
                        /// </param>
                        /// <returns type="Promise" locid="WinJS.UI.IListDataSource.insertAtStart_returnValue">
                        /// A Promise that contains the IItem that was added or an EditError if an error occurred.
                        /// </returns>
                        /// </signature>

                        // Add item to start of list, only notify if the first item was requested
                        return insertItem(
                            key, data,

                            // slotInsertBefore, append
                            (slotsStart.lastInSequence ? null : slotsStart.next), true,

                            // applyEdit
                            function () {
                                return listDataAdapter.insertAtStart(key, data);
                            }
                        );
                    };
                }

                if (listDataAdapter.insertBefore) {
                    this.insertBefore = function (key, data, nextKey) {
                        /// <signature helpKeyword="WinJS.UI.IListDataSource.insertBefore">
                        /// <summary locid="WinJS.UI.IListDataSource.insertBefore">
                        /// Inserts an item before another item.
                        /// </summary>
                        /// <param name="key" mayBeNull="true" type="String" locid="WinJS.UI.IListDataSource.insertBefore_p:key">
                        /// The key of the item to insert, if known; otherwise, null.
                        /// </param>
                        /// <param name="data" locid="WinJS.UI.IListDataSource.insertBefore_p:data">
                        /// The data for the item to insert.
                        /// </param>
                        /// <param name="nextKey" type="String" locid="WinJS.UI.IListDataSource.insertBefore_p:nextKey">
                        /// The key of an item in the data source. The new data is inserted before this item.
                        /// </param>
                        /// <returns type="Promise" locid="WinJS.UI.IListDataSource.insertBefore_returnValue">
                        /// A Promise that contains the IItem that was added or an EditError if an error occurred.
                        /// </returns>
                        /// </signature>

                        var slotNext = getSlotForEdit(nextKey);

                        // Add item before given item and send notification
                        return insertItem(
                            key, data,

                            // slotInsertBefore, append
                            slotNext, false,

                            // applyEdit
                            function () {
                                return listDataAdapter.insertBefore(key, data, nextKey, adjustedIndex(slotNext));
                            }
                        );
                    };
                }

                if (listDataAdapter.insertAfter) {
                    this.insertAfter = function (key, data, previousKey) {
                        /// <signature helpKeyword="WinJS.UI.IListDataSource.insertAfter">
                        /// <summary locid="WinJS.UI.IListDataSource.insertAfter">
                        /// Inserts an item after another item.
                        /// </summary>
                        /// <param name="key" mayBeNull="true" type="String" locid="WinJS.UI.IListDataSource.insertAfter_p:key">
                        /// The key of the item to insert, if known; otherwise, null.
                        /// </param>
                        /// <param name="data" locid="WinJS.UI.IListDataSource.insertAfter_p:data">
                        /// The data for the item to insert.
                        /// </param>
                        /// <param name="previousKey" type="String" locid="WinJS.UI.IListDataSource.insertAfter_p:previousKey">
                        /// The key for an item in the data source. The new item is added after this item.
                        /// </param>
                        /// <returns type="Promise" locid="WinJS.UI.IListDataSource.insertAfter_returnValue">
                        /// A Promise that contains the IItem that was added or an EditError if an error occurred.
                        /// </returns>
                        /// </signature>

                        var slotPrev = getSlotForEdit(previousKey);

                        // Add item after given item and send notification
                        return insertItem(
                            key, data,

                            // slotInsertBefore, append
                            (slotPrev ? slotPrev.next : null), true,

                            // applyEdit
                            function () {
                                return listDataAdapter.insertAfter(key, data, previousKey, adjustedIndex(slotPrev));
                            }
                        );
                    };
                }

                if (listDataAdapter.insertAtEnd) {
                    this.insertAtEnd = function (key, data) {
                        /// <signature helpKeyword="WinJS.UI.IListDataSource.insertAtEnd">
                        /// <summary locid="WinJS.UI.IListDataSource.insertAtEnd">
                        /// Adds an item to the end of the data source.
                        /// </summary>
                        /// <param name="key" mayBeNull="true" type="String" locid="WinJS.UI.IListDataSource.insertAtEnd_p:key">
                        /// The key of the item to insert, if known; otherwise, null.
                        /// </param>
                        /// <param name="data" locid="WinJS.UI.IListDataSource.insertAtEnd_data">
                        /// The data for the item to insert.
                        /// </param>
                        /// <returns type="Promise" locid="WinJS.UI.IListDataSource.insertAtEnd_returnValue">
                        /// A Promise that contains the IItem that was added or an EditError if an error occurred.
                        /// </returns>
                        /// </signature>

                        // Add item to end of list, only notify if the last item was requested
                        return insertItem(
                            key, data,

                            // slotInsertBefore, append
                            (slotListEnd.firstInSequence ? null : slotListEnd), false,

                            // applyEdit
                            function () {
                                return listDataAdapter.insertAtEnd(key, data);
                            }
                        );
                    };
                }

                if (listDataAdapter.change) {
                    this.change = function (key, newData) {
                        /// <signature helpKeyword="WinJS.UI.IListDataSource.change">
                        /// <summary locid="WinJS.UI.IListDataSource.change">
                        /// Overwrites the data of the specified item.
                        /// </summary>
                        /// <param name="key" type="String" locid="WinJS.UI.IListDataSource.change_p:key">
                        /// The key for the item to replace.
                        /// </param>
                        /// <param name="newData" type="Object" locid="WinJS.UI.IListDataSource.change_p:newData">
                        /// The new data for the item.
                        /// </param>
                        /// <returns type="Promise" locid="WinJS.UI.IListDataSource.change_returnValue">
                        /// A Promise that contains the IItem that was updated or an EditError if an error occurred.
                        /// </returns>
                        /// </signature>

                        var slot = getSlotForEdit(key);

                        return new Promise(function (complete, error) {
                            var itemOld;

                            queueEdit(
                                // applyEdit
                                function () {
                                    return listDataAdapter.change(key, newData, adjustedIndex(slot));
                                },

                                EditType.change, complete, error,

                                // keyUpdate
                                null,

                                // updateSlots
                                function () {
                                    itemOld = slot.item;

                                    slot.itemNew = {
                                        key: key,
                                        data: newData
                                    };

                                    if (itemOld) {
                                        changeSlot(slot);
                                    } else {
                                        completeFetchPromises(slot);
                                    }
                                },

                                // undo
                                function () {
                                    if (itemOld) {
                                        slot.itemNew = itemOld;
                                        changeSlot(slot);
                                    } else {
                                        beginRefresh();
                                    }
                                }
                            );
                        });
                    };
                }

                if (listDataAdapter.moveToStart) {
                    this.moveToStart = function (key) {
                        /// <signature helpKeyword="WinJS.UI.IListDataSource.moveToStart">
                        /// <summary locid="WinJS.UI.IListDataSource.moveToStart">
                        /// Moves the specified item to the beginning of the data source.
                        /// </summary>
                        /// <param name="key" type="String" locid="WinJS.UI.IListDataSource.moveToStart_p:key">
                        /// The key of the item to move.
                        /// </param>
                        /// <returns type="Promise" locid="WinJS.UI.IListDataSource.moveToStart_returnValue">
                        /// A Promise that contains the IItem that was moved or an EditError if an error occurred.
                        /// </returns>
                        /// </signature>

                        var slot = getSlotForEdit(key);

                        return moveItem(
                            slot,

                            // slotMoveBefore, append
                            slotsStart.next, true,

                            // applyEdit
                            function () {
                                return listDataAdapter.moveToStart(key, adjustedIndex(slot));
                            }
                        );
                    };
                }

                if (listDataAdapter.moveBefore) {
                    this.moveBefore = function (key, nextKey) {
                        /// <signature helpKeyword="WinJS.UI.IListDataSource.moveBefore">
                        /// <summary locid="WinJS.UI.IListDataSource.moveBefore">
                        /// Moves the specified item before another item.
                        /// </summary>
                        /// <param name="key" type="String" locid="WinJS.UI.IListDataSource.moveBefore_p:key">
                        /// The key of the item to move.
                        /// </param>
                        /// <param name="nextKey" type="String" locid="WinJS.UI.IListDataSource.moveBefore_p:nextKey">
                        /// The key of another item in the data source. The item specified by the key parameter
                        /// is moved to a position immediately before this item.
                        /// </param>
                        /// <returns type="Promise" locid="WinJS.UI.IListDataSource.moveBefore_returnValue">
                        /// A Promise that contains the IItem that was moved or an EditError if an error occurred.
                        /// </returns>
                        /// </signature>

                        var slot = getSlotForEdit(key),
                            slotNext = getSlotForEdit(nextKey);

                        return moveItem(
                            slot,

                            // slotMoveBefore, append
                            slotNext, false,

                            // applyEdit
                            function () {
                                return listDataAdapter.moveBefore(key, nextKey, adjustedIndex(slot), adjustedIndex(slotNext));
                            }
                        );
                    };
                }

                if (listDataAdapter.moveAfter) {
                    this.moveAfter = function (key, previousKey) {
                        /// <signature helpKeyword="WinJS.UI.IListDataSource.moveAfter">
                        /// <summary locid="WinJS.UI.IListDataSource.moveAfter">
                        /// Moves an item after another item.
                        /// </summary>
                        /// <param name="key" type="String" locid="WinJS.UI.IListDataSource.moveAfter_p:key">
                        /// The key of the item to move.
                        /// </param>
                        /// <param name="previousKey" type="String" locid="WinJS.UI.IListDataSource.moveAfter_p:previousKey">
                        /// The key of another item in the data source. The item specified by the key parameter will
                        /// is moved to a position immediately after this item.
                        /// </param>
                        /// <returns type="Promise" locid="WinJS.UI.IListDataSource.moveAfter_returnValue">
                        /// A Promise that contains the IItem that was moved or an EditError if an error occurred.
                        /// </returns>
                        /// </signature>

                        var slot = getSlotForEdit(key),
                            slotPrev = getSlotForEdit(previousKey);

                        return moveItem(
                            slot,

                            // slotMoveBefore, append
                            slotPrev.next, true,

                            // applyEdit
                            function () {
                                return listDataAdapter.moveAfter(key, previousKey, adjustedIndex(slot), adjustedIndex(slotPrev));
                            }
                        );
                    };
                }

                if (listDataAdapter.moveToEnd) {
                    this.moveToEnd = function (key) {
                        /// <signature helpKeyword="WinJS.UI.IListDataSource.moveToEnd">
                        /// <summary locid="WinJS.UI.IListDataSource.moveToEnd">
                        /// Moves an item to the end of the data source.
                        /// </summary>
                        /// <param name="key" type="String" locid="WinJS.UI.IListDataSource.moveToEnd_p:key">
                        /// The key of the item to move.
                        /// </param>
                        /// <returns type="Promise" locid="WinJS.UI.IListDataSource.moveToEnd_returnValue">
                        /// A Promise that contains the IItem that was moved or an EditError if an error occurred.
                        /// </returns>
                        /// </signature>

                        var slot = getSlotForEdit(key);

                        return moveItem(
                            slot,

                            // slotMoveBefore, append
                            slotListEnd, false,

                            // applyEdit
                            function () {
                                return listDataAdapter.moveToEnd(key, adjustedIndex(slot));
                            }
                        );
                    };
                }

                if (listDataAdapter.remove) {
                    this.remove = function (key) {
                        /// <signature helpKeyword="WinJS.UI.IListDataSource.remove">
                        /// <summary locid="WinJS.UI.IListDataSource.remove">
                        /// Removes an item from the data source.
                        /// </summary>
                        /// <param name="key" type="String" locid="WinJS.UI.IListDataSource.remove_p:key">
                        /// The key of the item to remove.
                        /// </param>
                        /// <returns type="Promise" locid="WinJS.UI.IListDataSource.remove_returnValue">
                        /// A Promise that contains nothing if the operation was successful or an EditError if an error occurred.
                        /// </returns>
                        /// </signature>

                        validateKey(key);

                        var slot = keyMap[key];

                        return new Promise(function (complete, error) {
                            var slotNext,
                                firstInSequence,
                                lastInSequence;

                            queueEdit(
                                // applyEdit
                                function () {
                                    return listDataAdapter.remove(key, adjustedIndex(slot));
                                },

                                EditType.remove, complete, error,

                                // keyUpdate
                                null,

                                // updateSlots
                                function () {
                                    if (slot) {
                                        slotNext = slot.next;
                                        firstInSequence = slot.firstInSequence;
                                        lastInSequence = slot.lastInSequence;

                                        updateNewIndices(slot, -1);
                                        deleteSlot(slot, false);
                                    }
                                },

                                // undo
                                function () {
                                    if (slot) {
                                        reinsertSlot(slot, slotNext, !firstInSequence, !lastInSequence);
                                        updateNewIndices(slot, 1);
                                        sendInsertedNotification(slot);
                                    }
                                }
                            );
                        });
                    };
                }

                this.endEdits = function () {
                    /// <signature helpKeyword="WinJS.UI.IListDataSource.endEdits">
                    /// <summary locid="WinJS.UI.IListDataSource.endEdits">
                    /// Notifies the data source that a sequence of edits has ended.  The data source will call
                    /// IListNotificationHandler.beginNotifications and endNotifications once each for a sequence of edits.
                    /// </summary>
                    /// </signature>

                    editsInProgress = false;
                    completeEdits();
                };

            } // _baseDataSourceConstructor

            var VDS = _Base.Class.define(function () {
                /// <signature helpKeyword="WinJS.UI.VirtualizedDataSource">
                /// <summary locid="WinJS.UI.VirtualizedDataSource">
                /// Use as a base class when defining a custom data source. Do not instantiate directly.
                /// </summary>
                /// <event name="statuschanged" locid="WinJS.UI.VirtualizedDataSource_e:statuschanged">
                /// Raised when the status of the VirtualizedDataSource changes between ready, waiting, and failure states.
                /// </event>
                /// </signature>
            }, {
                _baseDataSourceConstructor: _baseDataSourceConstructor,
                _isVirtualizedDataSource: true
            }, { // Static Members
                supportedForProcessing: false
            });
            _Base.Class.mix(VDS, _Events.eventMixin);
            return VDS;
        })

    });

});
// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Group Data Source

define('WinJS/VirtualizedDataSource/_GroupDataSource',[
    'exports',
    '../Core/_Base',
    '../Core/_ErrorFromName',
    '../Promise',
    '../Scheduler',
    '../Utilities/_UI',
    './_VirtualizedDataSourceImpl'
    ], function groupDataSourceInit(exports, _Base, _ErrorFromName, Promise, Scheduler, _UI, VirtualizedDataSource) {
    "use strict";

    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {

        _GroupDataSource: _Base.Namespace._lazy(function () {

            // Private statics

            function errorDoesNotExist() {
                return new _ErrorFromName(_UI.FetchError.doesNotExist);
            }

            var batchSizeDefault = 101;

            function groupReady(group) {
                return group && group.firstReached && group.lastReached;
            }

            var ListNotificationHandler = _Base.Class.define(function ListNotificationHandler_ctor(groupDataAdapter) {
                // Constructor

                this._groupDataAdapter = groupDataAdapter;
            }, {
                // Public methods

                beginNotifications: function () {
                },

                // itemAvailable: not implemented

                inserted: function (itemPromise, previousHandle, nextHandle) {
                    this._groupDataAdapter._inserted(itemPromise, previousHandle, nextHandle);
                },

                changed: function (newItem, oldItem) {
                    this._groupDataAdapter._changed(newItem, oldItem);
                },

                moved: function (itemPromise, previousHandle, nextHandle) {
                    this._groupDataAdapter._moved(itemPromise, previousHandle, nextHandle);
                },

                removed: function (handle, mirage) {
                    this._groupDataAdapter._removed(handle, mirage);
                },

                countChanged: function (newCount, oldCount) {
                    if (newCount === 0 && oldCount !== 0) {
                        this._groupDataAdapter.invalidateGroups();
                    }
                },

                indexChanged: function (handle, newIndex, oldIndex) {
                    this._groupDataAdapter._indexChanged(handle, newIndex, oldIndex);
                },

                endNotifications: function () {
                    this._groupDataAdapter._endNotifications();
                },

                reload: function () {
                    this._groupDataAdapter._reload();
                }
            }, {
                supportedForProcessing: false,
            });

            var GroupDataAdapter = _Base.Class.define(function GroupDataAdapater_ctor(listDataSource, groupKey, groupData, options) {
                // Constructor

                this._listBinding = listDataSource.createListBinding(new ListNotificationHandler(this));

                this._groupKey = groupKey;
                this._groupData = groupData;

                // _initializeState clears the count, so call this before processing the groupCountEstimate option
                this._initializeState();

                this._batchSize = batchSizeDefault;
                this._count = null;

                if (options) {
                    if (typeof options.groupCountEstimate === "number") {
                        this._count = (options.groupCountEstimate < 0 ? null : Math.max(options.groupCountEstimate, 1));
                    }
                    if (typeof options.batchSize === "number") {
                        this._batchSize = options.batchSize + 1;
                    }
                }

                if (this._listBinding.last) {
                    this.itemsFromEnd = function (count) {
                        var that = this;
                        return this._fetchItems(
                            // getGroup
                            function () {
                                return that._lastGroup;
                            },

                            // mayExist
                            function (failed) {
                                if (failed) {
                                    return false;
                                }
                                var count = that._count;
                                if (+count !== count) {
                                    return true;
                                }
                                if (count > 0) {
                                    return true;
                                }
                            },

                            // fetchInitialBatch
                            function () {
                                that._fetchBatch(that._listBinding.last(), that._batchSize - 1, 0);
                            },

                            count - 1, 0
                        );
                    };
                }
            }, {
                // Public members

                setNotificationHandler: function (notificationHandler) {
                    this._listDataNotificationHandler = notificationHandler;
                },

                // The ListDataSource should always compare these items by identity; in rare cases, it will do some unnecessary
                // rerendering, but at least fetching will not stringify items we already know to be valid and that we know
                // have not changed.
                compareByIdentity: true,

                // itemsFromStart: not implemented

                // itemsFromEnd: implemented in constructor

                itemsFromKey: function (key, countBefore, countAfter, hints) {
                    var that = this;
                    return this._fetchItems(
                        // getGroup
                        function () {
                            return that._keyMap[key];
                        },

                        // mayExist
                        function () {
                            var lastGroup = that._lastGroup;
                            if (!lastGroup) {
                                return true;
                            }
                            if (+lastGroup.index !== lastGroup.index) {
                                return true;
                            }
                        },

                        // fetchInitialBatch
                        function () {
                            hints = hints || {};
                            var itemPromise = (
                                typeof hints.groupMemberKey === "string" && that._listBinding.fromKey ?
                                    that._listBinding.fromKey(hints.groupMemberKey) :
                                typeof hints.groupMemberIndex === "number" && that._listBinding.fromIndex ?
                                    that._listBinding.fromIndex(hints.groupMemberIndex) :
                                hints.groupMemberDescription !== undefined && that._listBinding.fromDescription ?
                                    that._listBinding.fromDescription(hints.groupMemberDescription) :
                                    that._listBinding.first()
                            );

                            var fetchBefore = Math.floor(0.5 * (that._batchSize - 1));
                            that._fetchBatch(itemPromise, fetchBefore, that._batchSize - 1 - fetchBefore);
                        },

                        countBefore, countAfter
                    );
                },

                itemsFromIndex: function (index, countBefore, countAfter) {
                    var that = this;
                    return this._fetchItems(
                        // getGroup
                        function () {
                            return that._indexMap[index];
                        },

                        // mayExist
                        function () {
                            var lastGroup = that._lastGroup;
                            if (!lastGroup) {
                                return true;
                            }
                            if (+lastGroup.index !== lastGroup.index) {
                                return true;
                            }
                            if (index <= lastGroup.index) {
                                return true;
                            }
                        },

                        // fetchInitialBatch
                        function () {
                            that._fetchNextIndex();
                        },

                        countBefore, countAfter
                    );
                },

                // itemsFromDescription: not implemented

                getCount: function () {
                    if (this._lastGroup && typeof this._lastGroup.index === "number") {
                        return Promise.wrap(this._count);
                    } else {
                        // Even if there's a current estimate for _count, consider this call to be a request to determine the true
                        // count.

                        var that = this;
                        var countPromise = new Promise(function (complete) {
                            var fetch = {
                                initialBatch: function () {
                                    that._fetchNextIndex();
                                },
                                getGroup: function () { return null; },
                                countBefore: 0,
                                countAfter: 0,
                                complete: function (failed) {
                                    if (failed) {
                                        that._count = 0;
                                    }

                                    var count = that._count;
                                    if (typeof count === "number") {
                                        complete(count);
                                        return true;
                                    } else {
                                        return false;
                                    }
                                }
                            };

                            that._fetchQueue.push(fetch);

                            if (!that._itemBatch) {
                                that._continueFetch(fetch);
                            }
                        });

                        return (typeof this._count === "number" ? Promise.wrap(this._count) : countPromise);
                    }
                },

                invalidateGroups: function () {
                    this._beginRefresh();
                    this._initializeState();
                },

                // Editing methods not implemented

                // Private members

                _initializeState: function () {
                    this._count = null;
                    this._indexMax = null;

                    this._keyMap = {};
                    this._indexMap = {};
                    this._lastGroup = null;
                    this._handleMap = {};

                    this._fetchQueue = [];

                    this._itemBatch = null;
                    this._itemsToFetch = 0;

                    this._indicesChanged = false;
                },

                _releaseItem: function (item) {
                    delete this._handleMap[item.handle];
                    this._listBinding.releaseItem(item);
                },

                _processBatch: function () {
                    var previousItem = null,
                        previousGroup = null,
                        firstItemInGroup = null,
                        itemsSinceStart = 0,
                        failed = true;
                    for (var i = 0; i < this._batchSize; i++) {
                        var item = this._itemBatch[i],
                            groupKey = (item ? this._groupKey(item) : null);

                        if (item) {
                            failed = false;
                        }

                        if (previousGroup && groupKey !== null && groupKey === previousGroup.key) {
                            // This item is in the same group as the last item.  The only thing to do is advance the group's
                            // lastItem if this is definitely the last item that has been processed for the group.
                            itemsSinceStart++;
                            if (previousGroup.lastItem === previousItem) {
                                if (previousGroup.lastItem.handle !== previousGroup.firstItem.handle) {
                                    this._releaseItem(previousGroup.lastItem);
                                }
                                previousGroup.lastItem = item;
                                this._handleMap[item.handle] = previousGroup;

                                previousGroup.size++;
                            } else if (previousGroup.firstItem === item) {
                                if (previousGroup.firstItem.handle !== previousGroup.lastItem.handle) {
                                    this._releaseItem(previousGroup.firstItem);
                                }
                                previousGroup.firstItem = firstItemInGroup;
                                this._handleMap[firstItemInGroup.handle] = previousGroup;

                                previousGroup.size += itemsSinceStart;
                            }
                        } else {
                            var index = null;

                            if (previousGroup) {
                                previousGroup.lastReached = true;

                                if (typeof previousGroup.index === "number") {
                                    index = previousGroup.index + 1;
                                }
                            }

                            if (item) {
                                // See if the current group has already been processed
                                var group = this._keyMap[groupKey];

                                if (!group) {
                                    group = {
                                        key: groupKey,
                                        data: this._groupData(item),
                                        firstItem: item,
                                        lastItem: item,
                                        size: 1
                                    };
                                    this._keyMap[group.key] = group;
                                    this._handleMap[item.handle] = group;
                                }

                                if (i > 0) {
                                    group.firstReached = true;

                                    if (!previousGroup) {
                                        index = 0;
                                    }
                                }

                                if (typeof group.index !== "number" && typeof index === "number") {
                                    // Set the indices of as many groups as possible
                                    for (var group2 = group; group2; group2 = this._nextGroup(group2)) {
                                        group2.index = index;
                                        this._indexMap[index] = group2;

                                        index++;
                                    }

                                    this._indexMax = index;
                                    if (typeof this._count === "number" && !this._lastGroup && this._count <= this._indexMax) {
                                        this._count = this._indexMax + 1;
                                    }
                                }

                                firstItemInGroup = item;
                                itemsSinceStart = 0;

                                previousGroup = group;
                            } else {
                                if (previousGroup) {
                                    this._lastGroup = previousGroup;

                                    if (typeof previousGroup.index === "number") {
                                        this._count = (previousGroup.index + 1);
                                    }

                                    // Force a client refresh (which should be fast) to ensure that a countChanged notification is
                                    // sent.
                                    this._listDataNotificationHandler.invalidateAll();

                                    previousGroup = null;
                                }
                            }
                        }

                        previousItem = item;
                    }

                    // See how many fetches have now completed
                    var fetch;
                    for (fetch = this._fetchQueue[0]; fetch && fetch.complete(failed) ; fetch = this._fetchQueue[0]) {
                        this._fetchQueue.splice(0, 1);
                    }

                    // Continue work on the next fetch, if any
                    if (fetch) {
                        var that = this;
                        // Avoid reentering _processBatch
                        Scheduler.schedule(function GroupDataSource_async_processBatch() {
                            that._continueFetch(fetch);
                        }, Scheduler.Priority.normal, null, "WinJS.UI._GroupDataSource._continueFetch");
                    } else {
                        this._itemBatch = null;
                    }
                },

                _processPromise: function (itemPromise, batchIndex) {
                    itemPromise.retain();

                    this._itemBatch[batchIndex] = itemPromise;

                    var that = this;
                    itemPromise.then(function (item) {
                        that._itemBatch[batchIndex] = item;
                        if (--that._itemsToFetch === 0) {
                            that._processBatch();
                        }
                    });
                },

                _fetchBatch: function (itemPromise, countBefore) {
                    this._itemBatch = new Array(this._batchSize);
                    this._itemsToFetch = this._batchSize;

                    this._processPromise(itemPromise, countBefore);

                    var batchIndex;

                    this._listBinding.jumpToItem(itemPromise);
                    for (batchIndex = countBefore - 1; batchIndex >= 0; batchIndex--) {
                        this._processPromise(this._listBinding.previous(), batchIndex);
                    }

                    this._listBinding.jumpToItem(itemPromise);
                    for (batchIndex = countBefore + 1; batchIndex < this._batchSize; batchIndex++) {
                        this._processPromise(this._listBinding.next(), batchIndex);
                    }
                },

                _fetchAdjacent: function (item, after) {
                    // Batches overlap by one so group boundaries always fall within at least one batch
                    this._fetchBatch(
                        (this._listBinding.fromKey ? this._listBinding.fromKey(item.key) : this._listBinding.fromIndex(item.index)),
                        (after ? 0 : this._batchSize - 1),
                        (after ? this._batchSize - 1 : 0)
                    );
                },

                _fetchNextIndex: function () {
                    var groupHighestIndex = this._indexMap[this._indexMax - 1];
                    if (groupHighestIndex) {
                        // We've already fetched some of the first items, so continue where we left off
                        this._fetchAdjacent(groupHighestIndex.lastItem, true);
                    } else {
                        // Fetch one non-existent item before the list so _processBatch knows the start was reached
                        this._fetchBatch(this._listBinding.first(), 1, this._batchSize - 2);
                    }
                },

                _continueFetch: function (fetch) {
                    if (fetch.initialBatch) {
                        fetch.initialBatch();
                        fetch.initialBatch = null;
                    } else {
                        var group = fetch.getGroup();
                        if (group) {
                            var groupPrev,
                                groupNext;

                            if (!group.firstReached) {
                                this._fetchAdjacent(group.firstItem, false);
                            } else if (!group.lastReached) {
                                this._fetchAdjacent(group.lastItem, true);
                            } else if (fetch.countBefore > 0 && group.index !== 0 && !groupReady(groupPrev = this._previousGroup(group))) {
                                this._fetchAdjacent((groupPrev && groupPrev.lastReached ? groupPrev.firstItem : group.firstItem), false);
                            } else {
                                groupNext = this._nextGroup(group);
                                this._fetchAdjacent((groupNext && groupNext.firstReached ? groupNext.lastItem : group.lastItem), true);
                            }
                        } else {
                            // Assume we're searching for a key, index or the count by brute force
                            this._fetchNextIndex();
                        }
                    }
                },

                _fetchComplete: function (group, countBefore, countAfter, firstRequest, complete) {
                    if (groupReady(group)) {
                        // Check if the minimal requirements for the request are met
                        var groupPrev = this._previousGroup(group);
                        if (firstRequest || groupReady(groupPrev) || group.index === 0 || countBefore === 0) {
                            var groupNext = this._nextGroup(group);
                            if (firstRequest || groupReady(groupNext) || this._lastGroup === group || countAfter === 0) {
                                // Time to return the fetch results

                                // Find the first available group to return (don't return more than asked for)
                                var countAvailableBefore = 0,
                                    groupFirst = group;
                                while (countAvailableBefore < countBefore) {
                                    groupPrev = this._previousGroup(groupFirst);

                                    if (!groupReady(groupPrev)) {
                                        break;
                                    }

                                    groupFirst = groupPrev;
                                    countAvailableBefore++;
                                }

                                // Find the last available group to return
                                var countAvailableAfter = 0,
                                    groupLast = group;
                                while (countAvailableAfter < countAfter) {
                                    groupNext = this._nextGroup(groupLast);

                                    if (!groupReady(groupNext)) {
                                        break;
                                    }

                                    groupLast = groupNext;
                                    countAvailableAfter++;
                                }

                                // Now create the items to return
                                var len = countAvailableBefore + 1 + countAvailableAfter,
                                    items = new Array(len);

                                for (var i = 0; i < len; i++) {
                                    var item = {
                                        key: groupFirst.key,
                                        data: groupFirst.data,
                                        firstItemKey: groupFirst.firstItem.key,
                                        groupSize: groupFirst.size
                                    };

                                    var firstItemIndex = groupFirst.firstItem.index;
                                    if (typeof firstItemIndex === "number") {
                                        item.firstItemIndexHint = firstItemIndex;
                                    }

                                    items[i] = item;

                                    groupFirst = this._nextGroup(groupFirst);
                                }

                                var result = {
                                    items: items,
                                    offset: countAvailableBefore
                                };

                                result.totalCount = (
                                    typeof this._count === "number" ?
                                        this._count :
                                        _UI.CountResult.unknown
                                );

                                if (typeof group.index === "number") {
                                    result.absoluteIndex = group.index;
                                }

                                if (groupLast === this._lastGroup) {
                                    result.atEnd = true;
                                }

                                complete(result);
                                return true;
                            }
                        }
                    }

                    return false;
                },

                _fetchItems: function (getGroup, mayExist, fetchInitialBatch, countBefore, countAfter) {
                    var that = this;
                    return new Promise(function (complete, error) {
                        var group = getGroup(),
                            firstRequest = !group,
                            failureCount = 0;

                        function fetchComplete(failed) {
                            var group2 = getGroup();

                            if (group2) {
                                return that._fetchComplete(group2, countBefore, countAfter, firstRequest, complete, error);
                            } else if (firstRequest && !mayExist(failed)) {
                                error(errorDoesNotExist());
                                return true;
                            } else if (failureCount > 2) {
                                error(errorDoesNotExist());
                                return true;
                            } else {
                                // only consider consecutive failures
                                if (failed) {
                                    failureCount++;
                                } else {
                                    failureCount = 0;
                                }
                                // _continueFetch will switch to a brute force search
                                return false;
                            }
                        }

                        if (!fetchComplete()) {
                            var fetch = {
                                initialBatch: firstRequest ? fetchInitialBatch : null,
                                getGroup: getGroup,
                                countBefore: countBefore,
                                countAfter: countAfter,
                                complete: fetchComplete
                            };

                            that._fetchQueue.push(fetch);

                            if (!that._itemBatch) {
                                that._continueFetch(fetch);
                            }
                        }
                    });
                },

                _previousGroup: function (group) {
                    if (group && group.firstReached) {
                        this._listBinding.jumpToItem(group.firstItem);

                        return this._handleMap[this._listBinding.previous().handle];
                    } else {
                        return null;
                    }
                },

                _nextGroup: function (group) {
                    if (group && group.lastReached) {
                        this._listBinding.jumpToItem(group.lastItem);

                        return this._handleMap[this._listBinding.next().handle];
                    } else {
                        return null;
                    }
                },

                _invalidateIndices: function (group) {
                    this._count = null;
                    this._lastGroup = null;

                    if (typeof group.index === "number") {
                        this._indexMax = (group.index > 0 ? group.index : null);
                    }

                    // Delete the indices of this and all subsequent groups
                    for (var group2 = group; group2 && typeof group2.index === "number"; group2 = this._nextGroup(group2)) {
                        delete this._indexMap[group2.index];
                        group2.index = null;
                    }
                },

                _releaseGroup: function (group) {
                    this._invalidateIndices(group);

                    delete this._keyMap[group.key];

                    if (this._lastGroup === group) {
                        this._lastGroup = null;
                    }

                    if (group.firstItem !== group.lastItem) {
                        this._releaseItem(group.firstItem);
                    }
                    this._releaseItem(group.lastItem);
                },

                _beginRefresh: function () {
                    // Abandon all current fetches

                    this._fetchQueue = [];

                    if (this._itemBatch) {
                        for (var i = 0; i < this._batchSize; i++) {
                            var item = this._itemBatch[i];
                            if (item) {
                                if (item.cancel) {
                                    item.cancel();
                                }
                                this._listBinding.releaseItem(item);
                            }
                        }

                        this._itemBatch = null;
                    }

                    this._itemsToFetch = 0;

                    this._listDataNotificationHandler.invalidateAll();
                },

                _processInsertion: function (item, previousHandle, nextHandle) {
                    var groupPrev = this._handleMap[previousHandle],
                        groupNext = this._handleMap[nextHandle],
                        groupKey = null;

                    if (groupPrev) {
                        // If an item in a different group from groupPrev is being inserted after it, no need to discard groupPrev
                        if (!groupPrev.lastReached || previousHandle !== groupPrev.lastItem.handle || (groupKey = this._groupKey(item)) === groupPrev.key) {
                            this._releaseGroup(groupPrev);
                        } else if (this._lastGroup === groupPrev) {
                            this._lastGroup = null;
                            this._count = null;
                        }
                        this._beginRefresh();
                    }

                    if (groupNext && groupNext !== groupPrev) {
                        this._invalidateIndices(groupNext);

                        // If an item in a different group from groupNext is being inserted before it, no need to discard groupNext
                        if (!groupNext.firstReached || nextHandle !== groupNext.firstItem.handle || (groupKey !== null ? groupKey : this._groupKey(item)) === groupNext.key) {
                            this._releaseGroup(groupNext);
                        }
                        this._beginRefresh();
                    }
                },

                _processRemoval: function (handle) {
                    var group = this._handleMap[handle];

                    if (group && (handle === group.firstItem.handle || handle === group.lastItem.handle)) {
                        this._releaseGroup(group);
                        this._beginRefresh();
                    } else if (this._itemBatch) {
                        for (var i = 0; i < this._batchSize; i++) {
                            var item = this._itemBatch[i];
                            if (item && item.handle === handle) {
                                this._beginRefresh();
                                break;
                            }
                        }
                    }
                },

                _inserted: function (itemPromise, previousHandle, nextHandle) {
                    var that = this;
                    itemPromise.then(function (item) {
                        that._processInsertion(item, previousHandle, nextHandle);
                    });
                },

                _changed: function (newItem, oldItem) {
                    // A change to the first item could affect the group item
                    var group = this._handleMap[newItem.handle];
                    if (group && newItem.handle === group.firstItem.handle) {
                        this._releaseGroup(group);
                        this._beginRefresh();
                    }

                    // If the item is now in a different group, treat this as a move
                    if (this._groupKey(newItem) !== this._groupKey(oldItem)) {
                        this._listBinding.jumpToItem(newItem);
                        var previousHandle = this._listBinding.previous().handle;
                        this._listBinding.jumpToItem(newItem);
                        var nextHandle = this._listBinding.next().handle;

                        this._processRemoval(newItem.handle);
                        this._processInsertion(newItem, previousHandle, nextHandle);
                    }
                },

                _moved: function (itemPromise, previousHandle, nextHandle) {
                    this._processRemoval(itemPromise.handle);

                    var that = this;
                    itemPromise.then(function (item) {
                        that._processInsertion(item, previousHandle, nextHandle);
                    });
                },

                _removed: function (handle, mirage) {
                    // Mirage removals will just result in null items, which can be ignored
                    if (!mirage) {
                        this._processRemoval(handle);
                    }
                },

                _indexChanged: function (handle, newIndex, oldIndex) {
                    if (typeof oldIndex === "number") {
                        this._indicesChanged = true;
                    }
                },

                _endNotifications: function () {
                    if (this._indicesChanged) {
                        this._indicesChanged = false;

                        // Update the group sizes
                        for (var key in this._keyMap) {
                            var group = this._keyMap[key];

                            if (group.firstReached && group.lastReached) {
                                var newSize = group.lastItem.index + 1 - group.firstItem.index;
                                if (!isNaN(newSize)) {
                                    group.size = newSize;
                                }
                            }
                        }

                        // Invalidate the client, since some firstItemIndexHint properties have probably changed
                        this._beginRefresh();
                    }
                },

                _reload: function () {
                    this._initializeState();
                    this._listDataNotificationHandler.reload();
                }
            }, {
                supportedForProcessing: false,
            });

            return _Base.Class.derive(VirtualizedDataSource.VirtualizedDataSource, function (listDataSource, groupKey, groupData, options) {
                var groupDataAdapter = new GroupDataAdapter(listDataSource, groupKey, groupData, options);

                this._baseDataSourceConstructor(groupDataAdapter);

                this.extensions = {
                    invalidateGroups: function () {
                        groupDataAdapter.invalidateGroups();
                    }
                };
            }, {
                /* empty */
            }, {
                supportedForProcessing: false,
            });
        })

    });

});


// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Grouped Item Data Source

define('WinJS/VirtualizedDataSource/_GroupedItemDataSource',[
    '../Core/_Base',
    './_GroupDataSource'
    ], function groupedItemDataSourceInit(_Base, _GroupDataSource) {
    "use strict";

    _Base.Namespace.define("WinJS.UI", {

        computeDataSourceGroups: function (listDataSource, groupKey, groupData, options) {
            /// <signature helpKeyword="WinJS.UI.computeDataSourceGroups">
            /// <summary locid="WinJS.UI.computeDataSourceGroups">
            /// Returns a data source that adds group information to the items of another data source.  The "groups" property
            /// of this data source evaluates to yet another data source that enumerates the groups themselves.
            /// </summary>
            /// <param name="listDataSource" type="VirtualizedDataSource" locid="WinJS.UI.computeDataSourceGroups_p:listDataSource">
            /// The data source for the individual items to group.
            /// </param>
            /// <param name="groupKey" type="Function" locid="WinJS.UI.computeDataSourceGroups_p:groupKey">
            /// A callback function that takes an item in the list as an argument. The function is called
            /// for each item in the list and returns the group key for the item as a string.
            /// </param>
            /// <param name="groupData" type="Function" locid="WinJS.UI.computeDataSourceGroups_p:groupData">
            /// A callback function that takes an item in the IListDataSource as an argument.
            /// The function is called on one item in each group and returns
            /// an object that represents the header of that group.
            /// </param>
            /// <param name="options" type="Object" locid="WinJS.UI.computeDataSourceGroups_p:options">
            /// An object that can contain properties that specify additional options:
            ///
            /// groupCountEstimate:
            /// A Number value that is the initial estimate for the number of groups. If you specify -1,
            /// this function returns no result is until the actual number of groups
            /// has been determined.
            ///
            /// batchSize:
            /// A Number greater than 0 that specifies the number of items to fetch during each processing pass when
            /// searching for groups. (In addition to the number specified, one item from the previous batch
            /// is always included.)
            /// </param>
            /// <returns type="IListDataSource" locid="WinJS.UI.computeDataSourceGroups_returnValue">
            /// An IListDataSource that contains the items in the original data source and provides additional
            /// group info in a "groups" property. The "groups" property returns another
            /// IListDataSource that enumerates the different groups in the list.
            /// </returns>
            /// </signature>

            var groupedItemDataSource = Object.create(listDataSource);

            function createGroupedItem(item) {
                if (item) {
                    var groupedItem = Object.create(item);

                    groupedItem.groupKey = groupKey(item);

                    if (groupData) {
                        groupedItem.groupData = groupData(item);
                    }

                    return groupedItem;
                } else {
                    return null;
                }
            }

            function createGroupedItemPromise(itemPromise) {
                var groupedItemPromise = Object.create(itemPromise);

                groupedItemPromise.then = function (onComplete, onError, onCancel) {
                    return itemPromise.then(function (item) {
                        return onComplete(createGroupedItem(item));
                    }, onError, onCancel);
                };

                return groupedItemPromise;
            }

            groupedItemDataSource.createListBinding = function (notificationHandler) {
                var groupedNotificationHandler;

                if (notificationHandler) {
                    groupedNotificationHandler = Object.create(notificationHandler);

                    groupedNotificationHandler.inserted = function (itemPromise, previousHandle, nextHandle) {
                        return notificationHandler.inserted(createGroupedItemPromise(itemPromise), previousHandle, nextHandle);
                    };

                    groupedNotificationHandler.changed = function (newItem, oldItem) {
                        return notificationHandler.changed(createGroupedItem(newItem), createGroupedItem(oldItem));
                    };

                    groupedNotificationHandler.moved = function (itemPromise, previousHandle, nextHandle) {
                        return notificationHandler.moved(createGroupedItemPromise(itemPromise), previousHandle, nextHandle);
                    };
                } else {
                    groupedNotificationHandler = null;
                }

                var listBinding = listDataSource.createListBinding(groupedNotificationHandler),
                    groupedItemListBinding = Object.create(listBinding);

                var listBindingMethods = [
                    "first",
                    "last",
                    "fromDescription",
                    "jumpToItem",
                    "current"
                ];

                for (var i = 0, len = listBindingMethods.length; i < len; i++) {
                    (function (listBindingMethod) {
                        if (listBinding[listBindingMethod]) {
                            groupedItemListBinding[listBindingMethod] = function () {
                                return createGroupedItemPromise(listBinding[listBindingMethod].apply(listBinding, arguments));
                            };
                        }
                    })(listBindingMethods[i]);
                }

                // The following methods should be fast

                if (listBinding.fromKey) {
                    groupedItemListBinding.fromKey = function (key) {
                        return createGroupedItemPromise(listBinding.fromKey(key));
                    };
                }

                if (listBinding.fromIndex) {
                    groupedItemListBinding.fromIndex = function (index) {
                        return createGroupedItemPromise(listBinding.fromIndex(index));
                    };
                }

                groupedItemListBinding.prev = function () {
                    return createGroupedItemPromise(listBinding.prev());
                };

                groupedItemListBinding.next = function () {
                    return createGroupedItemPromise(listBinding.next());
                };

                return groupedItemListBinding;
            };

            var listDataSourceMethods = [
                "itemFromKey",
                "itemFromIndex",
                "itemFromDescription",
                "insertAtStart",
                "insertBefore",
                "insertAfter",
                "insertAtEnd",
                "change",
                "moveToStart",
                "moveBefore",
                "moveAfter",
                "moveToEnd"
                // remove does not return an itemPromise
            ];

            for (var i = 0, len = listDataSourceMethods.length; i < len; i++) {
                (function (listDataSourceMethod) {
                    if (listDataSource[listDataSourceMethod]) {
                        groupedItemDataSource[listDataSourceMethod] = function () {
                            return createGroupedItemPromise(listDataSource[listDataSourceMethod].apply(listDataSource, arguments));
                        };
                    }
                })(listDataSourceMethods[i]);
            }

            ["addEventListener", "removeEventListener", "dispatchEvent"].forEach(function (methodName) {
                if (listDataSource[methodName]) {
                    groupedItemDataSource[methodName] = function () {
                        return listDataSource[methodName].apply(listDataSource, arguments);
                    };
                }
            });

            var groupDataSource = null;

            Object.defineProperty(groupedItemDataSource, "groups", {
                get: function () {
                    if (!groupDataSource) {
                        groupDataSource = new _GroupDataSource._GroupDataSource(listDataSource, groupKey, groupData, options);
                    }
                    return groupDataSource;
                },
                enumerable: true,
                configurable: true
            });

            return groupedItemDataSource;
        }

    });

});


// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Storage Item Data Source

define('WinJS/VirtualizedDataSource/_StorageDataSource',[
    'exports',
    '../Core/_WinRT',
    '../Core/_Global',
    '../Core/_Base',
    '../Core/_ErrorFromName',
    '../Core/_WriteProfilerMark',
    '../Animations',
    '../Promise',
    '../Utilities/_UI',
    './_VirtualizedDataSourceImpl'
    ], function storageDataSourceInit(exports, _WinRT, _Global, _Base, _ErrorFromName, _WriteProfilerMark, Animations, Promise, _UI, VirtualizedDataSource) {
    "use strict";

    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {
        StorageDataSource: _Base.Namespace._lazy(function () {
            var StorageDataAdapter = _Base.Class.define(function StorageDataAdapter_ctor(query, options) {
                // Constructor
                _WriteProfilerMark("WinJS.UI.StorageDataSource:constructor,StartTM");

                var mode = _WinRT.Windows.Storage.FileProperties.ThumbnailMode.singleItem,
                    size = 256,
                    flags = _WinRT.Windows.Storage.FileProperties.ThumbnailOptions.useCurrentScale,
                    delayLoad = true,
                    library;

                if (query === "Pictures") {
                    mode = _WinRT.Windows.Storage.FileProperties.ThumbnailMode.picturesView;
                    library = _WinRT.Windows.Storage.KnownFolders.picturesLibrary;
                    size = 190;
                } else if (query === "Music") {
                    mode = _WinRT.Windows.Storage.FileProperties.ThumbnailMode.musicView;
                    library = _WinRT.Windows.Storage.KnownFolders.musicLibrary;
                    size = 256;
                } else if (query === "Documents") {
                    mode = _WinRT.Windows.Storage.FileProperties.ThumbnailMode.documentsView;
                    library = _WinRT.Windows.Storage.KnownFolders.documentsLibrary;
                    size = 40;
                } else if (query === "Videos") {
                    mode = _WinRT.Windows.Storage.FileProperties.ThumbnailMode.videosView;
                    library = _WinRT.Windows.Storage.KnownFolders.videosLibrary;
                    size = 190;
                }

                if (!library) {
                    this._query = query;
                } else {
                    var queryOptions = new _WinRT.Windows.Storage.Search.QueryOptions();
                    queryOptions.folderDepth = _WinRT.Windows.Storage.Search.FolderDepth.deep;
                    queryOptions.indexerOption = _WinRT.Windows.Storage.Search.IndexerOption.useIndexerWhenAvailable;
                    this._query = library.createFileQueryWithOptions(queryOptions);
                }

                if (options) {
                    if (typeof options.mode === "number") {
                        mode = options.mode;
                    }
                    if (typeof options.requestedThumbnailSize === "number") {
                        size = Math.max(1, Math.min(options.requestedThumbnailSize, 1024));
                    } else {
                        switch (mode) {
                            case _WinRT.Windows.Storage.FileProperties.ThumbnailMode.picturesView:
                            case _WinRT.Windows.Storage.FileProperties.ThumbnailMode.videosView:
                                size = 190;
                                break;
                            case _WinRT.Windows.Storage.FileProperties.ThumbnailMode.documentsView:
                            case _WinRT.Windows.Storage.FileProperties.ThumbnailMode.listView:
                                size = 40;
                                break;
                            case _WinRT.Windows.Storage.FileProperties.ThumbnailMode.musicView:
                            case _WinRT.Windows.Storage.FileProperties.ThumbnailMode.singleItem:
                                size = 256;
                                break;
                        }
                    }
                    if (typeof options.thumbnailOptions === "number") {
                        flags = options.thumbnailOptions;
                    }
                    if (typeof options.waitForFileLoad === "boolean") {
                        delayLoad = !options.waitForFileLoad;
                    }
                }

                this._loader = new _WinRT.Windows.Storage.BulkAccess.FileInformationFactory(this._query, mode, size, flags, delayLoad);
                this.compareByIdentity = false;
                this.firstDataRequest = true;
                _WriteProfilerMark("WinJS.UI.StorageDataSource:constructor,StopTM");
            }, {
                // Public members

                setNotificationHandler: function (notificationHandler) {
                    this._notificationHandler = notificationHandler;
                    this._query.addEventListener("contentschanged", function () {
                        notificationHandler.invalidateAll();
                    });
                    this._query.addEventListener("optionschanged", function () {
                        notificationHandler.invalidateAll();
                    });
                },

                itemsFromEnd: function (count) {
                    var that = this;
                    _WriteProfilerMark("WinJS.UI.StorageDataSource:itemsFromEnd,info");
                    return this.getCount().then(function (totalCount) {
                        if (totalCount === 0) {
                            return Promise.wrapError(new _ErrorFromName(_UI.FetchError.doesNotExist));
                        }
                        // Intentionally passing countAfter = 1 to go one over the end so that itemsFromIndex will
                        // report the vector size since its known.
                        return that.itemsFromIndex(totalCount - 1, Math.min(totalCount - 1, count - 1), 1);
                    });
                },

                itemsFromIndex: function (index, countBefore, countAfter) {
                    // don't allow more than 64 items to be retrieved at once
                    if (countBefore + countAfter > 64) {
                        countBefore = Math.min(countBefore, 32);
                        countAfter = 64 - (countBefore + 1);
                    }

                    var first = (index - countBefore),
                        count = (countBefore + 1 + countAfter);
                    var that = this;
                    // Fetch a minimum of 32 items on the first request for smoothness. Otherwise
                    // listview displays 2 items first and then the rest of the page.
                    if (that.firstDataRequest) {
                        that.firstDataRequest = false;
                        count = Math.max(count, 32);
                    }
                    function listener(ev) {
                        that._notificationHandler.changed(that._item(ev.target));
                    }

                    var perfId = "WinJS.UI.StorageDataSource:itemsFromIndex(" + first + "-" + (first + count - 1) + ")";
                    _WriteProfilerMark(perfId + ",StartTM");
                    return this._loader.getItemsAsync(first, count).then(function (itemsVector) {
                        var vectorSize = itemsVector.size;
                        if (vectorSize <= countBefore) {
                            return Promise.wrapError(new _ErrorFromName(_UI.FetchError.doesNotExist));
                        }
                        var items = new Array(vectorSize);
                        var localItemsVector = new Array(vectorSize);
                        itemsVector.getMany(0, localItemsVector);
                        for (var i = 0; i < vectorSize; i++) {
                            items[i] = that._item(localItemsVector[i]);
                            localItemsVector[i].addEventListener("propertiesupdated", listener);
                        }
                        var result = {
                            items: items,
                            offset: countBefore,
                            absoluteIndex: index
                        };
                        // set the totalCount only when we know it (when we retrieived fewer items than were asked for)
                        if (vectorSize < count) {
                            result.totalCount = first + vectorSize;
                        }
                        _WriteProfilerMark(perfId + ",StopTM");
                        return result;
                    });
                },

                itemsFromDescription: function (description, countBefore, countAfter) {
                    var that = this;
                    _WriteProfilerMark("WinJS.UI.StorageDataSource:itemsFromDescription,info");
                    return this._query.findStartIndexAsync(description).then(function (index) {
                        return that.itemsFromIndex(index, countBefore, countAfter);
                    });
                },

                getCount: function () {
                    _WriteProfilerMark("WinJS.UI.StorageDataSource:getCount,info");
                    return this._query.getItemCountAsync();
                },

                itemSignature: function (item) {
                    return item.folderRelativeId;
                },

                // compareByIdentity: set in constructor
                // itemsFromStart: not implemented
                // itemsFromKey: not implemented
                // insertAtStart: not implemented
                // insertBefore: not implemented
                // insertAfter: not implemented
                // insertAtEnd: not implemented
                // change: not implemented
                // moveToStart: not implemented
                // moveBefore: not implemented
                // moveAfter: not implemented
                // moveToEnd: not implemented
                // remove: not implemented

                // Private members

                _item: function (item) {
                    return {
                        key: item.path || item.folderRelativeId,
                        data: item
                    };
                }
            }, {
                supportedForProcessing: false,
            });

            return _Base.Class.derive(VirtualizedDataSource.VirtualizedDataSource, function (query, options) {
                /// <signature helpKeyword="WinJS.UI.StorageDataSource">
                /// <summary locid="WinJS.UI.StorageDataSource">
                /// Creates a data source that enumerates an IStorageQueryResultBase.
                /// </summary>
                /// <param name="query" type="Windows.Storage.Search.IStorageQueryResultBase" locid="WinJS.UI.StorageDataSource_p:query">
                /// The object to enumerate. It must support IStorageQueryResultBase.
                /// </param>
                /// <param name="options" mayBeNull="true" optional="true" type="Object" locid="WinJS.UI.StorageDataSource_p:options">
                /// An object that specifies options for the data source. This parameter is optional. It can contain these properties:
                ///
                /// mode:
                /// A Windows.Storage.FileProperties.ThumbnailMode - a value that specifies whether to request
                /// thumbnails and the type of thumbnails to request.
                ///
                /// requestedThumbnailSize:
                /// A Number that specifies the size of the thumbnails.
                ///
                /// thumbnailOptions:
                /// A Windows.Storage.FileProperties.ThumbnailOptions value that specifies additional options for the thumbnails.
                ///
                /// waitForFileLoad:
                /// If you set this to true, the data source returns items only after it loads their properties and thumbnails.
                ///
                /// </param>
                /// </signature>
                this._baseDataSourceConstructor(new StorageDataAdapter(query, options));
            }, {
                /* empty */
            }, {
                loadThumbnail: function (item, image) {
                    /// <signature>
                    /// <summary locid="WinJS.UI.StorageDataSource.loadThumbnail">
                    /// Returns a promise for an image element that completes when the full quality thumbnail of the provided item is drawn to the
                    /// image element.
                    /// </summary>
                    /// <param name="item" type="ITemplateItem" locid="WinJS.UI.StorageDataSource.loadThumbnail_p:item">
                    /// The item to retrieve a thumbnail for.
                    /// </param>
                    /// <param name="image" type="Object" domElement="true" optional="true" locid="WinJS.UI.StorageDataSource.loadThumbnail_p:image">
                    /// The image element to use. If not provided, a new image element is created.
                    /// </param>
                    /// </signature>
                    var thumbnailUpdateHandler,
                        thumbnailPromise,
                        shouldRespondToThumbnailUpdate = false;

                    return new Promise(function (complete) {
                        // Load a thumbnail if it exists. The promise completes when a full quality thumbnail is visible.
                        var tagSupplied = (image ? true : false);
                        var processThumbnail = function (thumbnail) {
                            if (thumbnail) {
                                var url = _Global.URL.createObjectURL(thumbnail, {oneTimeOnly: true});

                                // If this is the first version of the thumbnail we're loading, fade it in.
                                if (!thumbnailPromise) {
                                    thumbnailPromise = item.loadImage(url, image).then(function (image) {
                                        // Wrapping the fadeIn call in a promise for the image returned by loadImage allows us to
                                        // pipe the result of loadImage to further chained promises.  This is necessary because the
                                        // image element provided to loadThumbnail is optional, and loadImage will create an image
                                        // element if none is provided.
                                        return item.isOnScreen().then(function (visible) {
                                            var imagePromise;
                                            if (visible && tagSupplied) {
                                                imagePromise = Animations.fadeIn(image).then(function () {
                                                    return image;
                                                });
                                            } else {
                                                image.style.opacity = 1;
                                                imagePromise = Promise.wrap(image);
                                            }
                                            return imagePromise;
                                        });
                                    });
                                } else { // Otherwise, replace the existing version without animation.
                                    thumbnailPromise = thumbnailPromise.then(function (image) {
                                        return item.loadImage(url, image);
                                    });
                                }

                                // If we have the full resolution thumbnail, we can cancel further updates and complete the promise
                                // when current work is complete.
                                if ((thumbnail.type !== _WinRT.Windows.Storage.FileProperties.ThumbnailType.icon) && !thumbnail.returnedSmallerCachedSize) {
                                    _WriteProfilerMark("WinJS.UI.StorageDataSource:loadThumbnail complete,info");
                                    item.data.removeEventListener("thumbnailupdated", thumbnailUpdateHandler);
                                    shouldRespondToThumbnailUpdate = false;
                                    thumbnailPromise = thumbnailPromise.then(function (image) {
                                        thumbnailUpdateHandler = null;
                                        thumbnailPromise = null;
                                        complete(image);
                                    });
                                }
                            }
                        };

                        thumbnailUpdateHandler = function (e) {
                            // Ensure that a zombie update handler does not get invoked.
                            if (shouldRespondToThumbnailUpdate) {
                                processThumbnail(e.target.thumbnail);
                            }
                        };
                        item.data.addEventListener("thumbnailupdated", thumbnailUpdateHandler);
                        shouldRespondToThumbnailUpdate = true;

                        // If we already have a thumbnail we should render it now.
                        processThumbnail(item.data.thumbnail);
                    }, function () {
                        item.data.removeEventListener("thumbnailupdated", thumbnailUpdateHandler);
                        shouldRespondToThumbnailUpdate = false;
                        thumbnailUpdateHandler = null;
                        if (thumbnailPromise) {
                            thumbnailPromise.cancel();
                            thumbnailPromise = null;
                        }
                    });
                },

                supportedForProcessing: false,
            });
        })
    });

});
// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/VirtualizedDataSource',[
    './VirtualizedDataSource/_VirtualizedDataSourceImpl',
    './VirtualizedDataSource/_GroupDataSource',
    './VirtualizedDataSource/_GroupedItemDataSource',
    './VirtualizedDataSource/_StorageDataSource'
     ], function () {

    //wrapper module
});
define('require-style',{load: function(id){throw new Error("Dynamic load not allowed: " + id);}});

define('require-style!less/desktop/styles-intrinsic',[],function(){});

define('require-style!less/desktop/colors-intrinsic',[],function(){});

define('require-style!less/phone/styles-intrinsic',[],function(){});

define('require-style!less/phone/colors-intrinsic',[],function(){});
// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/IntrinsicControls',[
    '../Utilities/_Hoverable',
    'require-style!less/desktop/styles-intrinsic',
    'require-style!less/desktop/colors-intrinsic',
    'require-style!less/phone/styles-intrinsic',
    'require-style!less/phone/colors-intrinsic'
    ], function (_Hoverable) {
});
// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/ItemContainer/_Constants',[
    'exports',
    '../../Core/_Base'
    ], function constantsInit(exports, _Base) {
    "use strict";

    var members = {};
    members._listViewClass = "win-listview";
    members._listViewSupportsCrossSlideClass = "win-listview-supports-cross-slide";
    members._viewportClass = "win-viewport";
    members._rtlListViewClass = "win-rtl";
    members._horizontalClass = "win-horizontal";
    members._verticalClass = "win-vertical";
    members._scrollableClass = "win-surface";
    members._itemsContainerClass = "win-itemscontainer";
    members._padderClass = "win-itemscontainer-padder";
    members._proxyClass = "_win-proxy";
    members._itemClass = "win-item";
    members._itemBoxClass = "win-itembox";
    members._itemsBlockClass = "win-itemsblock";
    members._containerClass = "win-container";
    members._backdropClass = "win-backdrop";
    members._footprintClass = "win-footprint";
    members._groupsClass = "win-groups";
    members._selectedClass = "win-selected";
    members._swipeableClass = "win-swipeable";
    members._swipeClass = "win-swipe";
    members._selectionBorderClass = "win-selectionborder";
    members._selectionBackgroundClass = "win-selectionbackground";
    members._selectionCheckmarkClass = "win-selectioncheckmark";
    members._selectionCheckmarkBackgroundClass = "win-selectioncheckmarkbackground";
    members._pressedClass = "win-pressed";
    members._headerClass = "win-groupheader";
    members._headerContainerClass = "win-groupheadercontainer";
    members._groupLeaderClass = "win-groupleader";
    members._progressClass = "win-progress";
    members._selectionHintClass = "win-selectionhint";
    members._revealedClass = "win-revealed";
    members._itemFocusClass = "win-focused";
    members._itemFocusOutlineClass = "win-focusedoutline";
    members._zoomingXClass = "win-zooming-x";
    members._zoomingYClass = "win-zooming-y";
    members._listLayoutClass = "win-listlayout";
    members._gridLayoutClass = "win-gridlayout";
    members._headerPositionTopClass = "win-headerpositiontop";
    members._headerPositionLeftClass = "win-headerpositionleft";
    members._structuralNodesClass = "win-structuralnodes";
    members._uniformGridLayoutClass = "win-uniformgridlayout";
    members._uniformListLayoutClass = "win-uniformlistlayout";
    members._cellSpanningGridLayoutClass = "win-cellspanninggridlayout";
    members._laidOutClass = "win-laidout";
    members._nonDraggableClass = "win-nondraggable";
    members._nonSelectableClass = "win-nonselectable";
    members._nonSwipeableClass = "win-nonswipeable";
    members._dragOverClass = "win-dragover";
    members._dragSourceClass = "win-dragsource";
    members._clipClass = "win-clip";
    members._selectionModeClass = "win-selectionmode";
    members._noCSSGrid = "win-nocssgrid";

    members._INVALID_INDEX = -1;
    members._UNINITIALIZED = -1;

    members._LEFT_MSPOINTER_BUTTON = 0;
    members._RIGHT_MSPOINTER_BUTTON = 2;

    members._TAP_END_THRESHOLD = 10;

    members._DEFAULT_PAGES_TO_LOAD = 5;
    members._DEFAULT_PAGE_LOAD_THRESHOLD = 2;

    members._MIN_AUTOSCROLL_RATE = 150;
    members._MAX_AUTOSCROLL_RATE = 1500;
    members._AUTOSCROLL_THRESHOLD = 100;
    members._AUTOSCROLL_DELAY = 50;

    members._DEFERRED_ACTION = 250;
    members._DEFERRED_SCROLL_END = 250;

    // For horizontal layouts
    members._VERTICAL_SWIPE_SELECTION_THRESHOLD = 39;
    members._VERTICAL_SWIPE_SPEED_BUMP_START = 0;
    members._VERTICAL_SWIPE_SPEED_BUMP_END = 127;
    members._VERTICAL_SWIPE_SELF_REVEAL_GESTURE = 15;

    // For vertical layouts
    members._HORIZONTAL_SWIPE_SELECTION_THRESHOLD = 27;
    members._HORIZONTAL_SWIPE_SPEED_BUMP_START = 0;
    members._HORIZONTAL_SWIPE_SPEED_BUMP_END = 150;
    members._HORIZONTAL_SWIPE_SELF_REVEAL_GESTURE = 23;

    members._SELECTION_CHECKMARK = "\uE081";

    members._LISTVIEW_PROGRESS_DELAY = 2000;

    var ScrollToPriority = {
        uninitialized: 0,
        low: 1,             // used by layoutSite.invalidateLayout, forceLayout, _processReload, _update and _onMSElementResize - operations that preserve the scroll position
        medium: 2,          // used by dataSource change, layout change and etc - operations that reset the scroll position to 0
        high: 3             // used by indexOfFirstVisible, ensureVisible, scrollPosition - operations in which the developer explicitly sets the scroll position
    };

    var ViewChange = {
        rebuild: 0,
        remeasure: 1,
        relayout: 2,
        realize: 3
    };

    members._ScrollToPriority = ScrollToPriority;
    members._ViewChange = ViewChange;

    _Base.Namespace._moduleDefine(exports, "WinJS.UI", members);
});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/ItemContainer/_ItemEventsHandler',[
    'exports',
    '../../Core/_Global',
    '../../Core/_WinRT',
    '../../Core/_Base',
    '../../Core/_BaseUtils',
    '../../Core/_WriteProfilerMark',
    '../../Animations',
    '../../Animations/_TransitionAnimation',
    '../../Promise',
    '../../Utilities/_ElementUtilities',
    '../../Utilities/_UI',
    './_Constants'
    ], function itemEventsHandlerInit(exports, _Global, _WinRT, _Base, _BaseUtils, _WriteProfilerMark, Animations, _TransitionAnimation, Promise, _ElementUtilities, _UI, _Constants) {
    "use strict";

    var transformNames = _BaseUtils._browserStyleEquivalents["transform"];
    var MAX_TILT_ROTATION = 0.15;
    var MAX_TILT_SHRINK = 0.025;
    var uniqueID = _ElementUtilities._uniqueID;
    var MSManipulationEventStates = _ElementUtilities._MSManipulationEvent;

    function unitVector3d(v) {
        var mag = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
        return {
            x: v.x / mag,
            y: v.y / mag,
            z: v.z / mag
        };
    }

    // Returns a CSS rotation matrix which rotates by *angle* radians over *axis*.
    // *axis* is an object of the form: { x: number, y: number, z: number }
    function rotationTransform3d(angle, axis) {
        var u = unitVector3d(axis);
        var cos = Math.cos(angle);
        var sin = Math.sin(angle);
        var matrix = [
            cos + u.x * u.x * (1 - cos),
            u.x * u.y * (1 - cos) - u.z * sin,
            u.x * u.z * (1 - cos) + u.y * sin,
            0,

            u.y * u.x * (1 - cos) + u.z * sin,
            cos + u.y * u.y * (1 - cos),
            u.y * u.z * (1 - cos) - u.x * sin,
            0,

            u.z * u.x * (1 - cos) - u.y * sin,
            u.z * u.y * (1 - cos) + u.x * sin,
            cos + u.z * u.z * (1 - cos),
            0,

            0, 0, 0, 1
        ];

        // Scientific notation in transform values breaks the CSS3 values spec.
        matrix = matrix.map(function (value) {
            return value.toFixed(8);
        });
        return "matrix3d(" + matrix.join(",") + ")";
    }

    // Returns a CSS transformation to rotate and shrink an element when it is
    // pressed. The closer the click is to the center of the item, the more it
    // shrinks and the less it rotates.
    // *elementRect* should be of the form returned by getBoundingClientRect. All
    // of the parameters must be relative to the same coordinate system.
    // This function was translated from the Splash implementation.
    function tiltTransform(clickX, clickY, elementRect) {
        // x and y range from 0.0 thru 1.0 inclusive with the origin being at the top left.
        var x = _ElementUtilities._clamp((clickX - elementRect.left) / elementRect.width, 0, 1);
        var y = _ElementUtilities._clamp((clickY - elementRect.top) / elementRect.height, 0, 1);

        // Axis is perpendicular to the line drawn between the click position and the center of the item.
        // We set z to a small value so that even if x and y turn out to be 0, we still have an axis.
        var axis = {
            x: y - 0.5,
            y: -(x - 0.5),
            z: 0.0001
        };

        // The angle of the rotation is larger when the click is farther away from the center.
        var magnitude = Math.abs(x - 0.5) + Math.abs(y - 0.5); // an approximation
        var angle = magnitude * MAX_TILT_ROTATION;

        // The distance the control is pushed into z-space is larger when the click is closer to the center.
        var scale = 1 - (1 - magnitude) * MAX_TILT_SHRINK;

        var transform = "perspective(800px) scale(" + scale + ", " + scale + ") " + rotationTransform3d(angle, axis);

        return transform;
    }

    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {
        // Expose these to the unit tests
        _rotationTransform3d: rotationTransform3d,
        _tiltTransform: tiltTransform,

        _ItemEventsHandler: _Base.Namespace._lazy(function () {
            var PT_TOUCH = _ElementUtilities._MSPointerEvent.MSPOINTER_TYPE_TOUCH || "touch";

            function getElementWithClass(parent, className) {
                return parent.querySelector("." + className);
            }

            function createNodeWithClass(className, skipAriaHidden) {
                var element = _Global.document.createElement("div");
                element.className = className;
                if (!skipAriaHidden) {
                    element.setAttribute("aria-hidden", true);
                }
                return element;
            }

            var ItemEventsHandler =  _Base.Class.define(function ItemEventsHandler_ctor(site) {
                this._site = site;

                this._work = [];
                this._animations = {};
                this._selectionHintTracker = {};
                this._swipeClassTracker = {};

                // The gesture recognizer is used for SRG, which is not supported on Phone
                if (!_BaseUtils.isPhone && this._selectionAllowed()) {
                    var that = this;
                    _Global.setTimeout(function () {
                        if (!that._gestureRecognizer && !site.isZombie()) {
                            that._gestureRecognizer = that._createGestureRecognizer();
                        }
                    }, 500);
                }
            }, {
                dispose: function () {
                    if (this._disposed) {
                        return;
                    }
                    this._disposed = true;
                    this._gestureRecognizer = null;
                    _ElementUtilities._removeEventListener(_Global, "pointerup", this._resetPointerDownStateBound);
                    _ElementUtilities._removeEventListener(_Global, "pointercancel", this._resetPointerDownStateBound);
                },

                onMSManipulationStateChanged: function ItemEventsHandler_onMSManipulationStateChanged(eventObject) {
                    var state = eventObject.currentState;
                    // We're not necessarily guaranteed to get onMSPointerDown before we get a selection event from cross slide,
                    // so if we hit a select state with no pressed item box recorded, we need to set up the pressed info before
                    // processing the selection.
                    if (state === MSManipulationEventStates.MS_MANIPULATION_STATE_PRESELECT && !this._site.pressedItemBox) {
                        var currentPressedIndex = this._site.indexForItemElement(eventObject.target);

                        this._site.pressedEntity = { type: _UI.ObjectType.item, index: currentPressedIndex };
                        if (this._site.pressedEntity.index !== _Constants._INVALID_INDEX) {
                            this._site.pressedItemBox = this._site.itemBoxAtIndex(this._site.pressedEntity.index);
                            this._site.pressedContainer = this._site.containerAtIndex(this._site.pressedEntity.index);
                            this._site.animatedElement = _BaseUtils.isPhone ? this._site.pressedItemBox : this._site.pressedContainer;
                            this._site.pressedHeader = null;
                            var allowed = this._site.verifySelectionAllowed(this._site.pressedEntity);
                            this._canSelect = allowed.canSelect;
                            this._canTapSelect = allowed.canTapSelect;
                            this._swipeBehaviorSelectionChanged = false;
                            this._selectionHint = null;
                            if (this._canSelect) {
                                this._addSelectionHint();
                            }
                        }
                    }
                    if (this._canSelect && (state === MSManipulationEventStates.MS_MANIPULATION_STATE_PRESELECT ||
                        state === MSManipulationEventStates.MS_MANIPULATION_STATE_COMMITTED ||
                        state === MSManipulationEventStates.MS_MANIPULATION_STATE_CANCELLED ||
                        state === MSManipulationEventStates.MS_MANIPULATION_STATE_SELECTING ||
                        state === MSManipulationEventStates.MS_MANIPULATION_STATE_DRAGGING)) {
                        this._dispatchSwipeBehavior(state);
                    }

                    if (state === MSManipulationEventStates.MS_MANIPULATION_STATE_COMMITTED ||
                        state === MSManipulationEventStates.MS_MANIPULATION_STATE_CANCELLED ||
                        state === MSManipulationEventStates.MS_MANIPULATION_STATE_STOPPED) {
                        this.resetPointerDownState();
                    }
                },

                onPointerDown: function ItemEventsHandler_onPointerDown(eventObject) {
                    _WriteProfilerMark("WinJS.UI._ItemEventsHandler:MSPointerDown,StartTM");
                    var site = this._site,
                        touchInput = (eventObject.pointerType === PT_TOUCH),
                        leftButton,
                        rightButton;
                    site.pressedElement = eventObject.target;
                    if (_WinRT.Windows.UI.Input.PointerPoint) {
                        // xButton is true when you've x-clicked with a mouse or pen. Otherwise it is false.
                        var currentPoint = this._getCurrentPoint(eventObject);
                        var pointProps = currentPoint.properties;
                        if (!(touchInput || pointProps.isInverted || pointProps.isEraser || pointProps.isMiddleButtonPressed)) {
                            rightButton = pointProps.isRightButtonPressed;
                            leftButton = !rightButton && pointProps.isLeftButtonPressed;
                        } else {
                            leftButton = rightButton = false;
                        }
                    } else {
                        // xButton is true when you've x-clicked with a mouse. Otherwise it is false.
                        leftButton = (eventObject.button === _Constants._LEFT_MSPOINTER_BUTTON);
                        rightButton = (eventObject.button === _Constants._RIGHT_MSPOINTER_BUTTON);
                    }

                    this._DragStartBound = this._DragStartBound || this.onDragStart.bind(this);
                    this._PointerEnterBound = this._PointerEnterBound || this.onPointerEnter.bind(this);
                    this._PointerLeaveBound = this._PointerLeaveBound || this.onPointerLeave.bind(this);

                    this._swipeBehaviorState = MSManipulationEventStates.MS_MANIPULATION_STATE_STOPPED;
                    var swipeEnabled = site.swipeBehavior === _UI.SwipeBehavior.select,
                        isInteractive = this._isInteractive(eventObject.target),
                        currentPressedIndex = site.indexForItemElement(eventObject.target),
                        currentPressedHeaderIndex = site.indexForHeaderElement(eventObject.target),
                        mustSetCapture = !isInteractive && currentPressedIndex !== _Constants._INVALID_INDEX;

                    if ((touchInput || leftButton || (this._selectionAllowed() && swipeEnabled && rightButton)) && this._site.pressedEntity.index === _Constants._INVALID_INDEX && !isInteractive) {
                        if (currentPressedHeaderIndex === _Constants._INVALID_INDEX) {
                            this._site.pressedEntity = { type: _UI.ObjectType.item, index: currentPressedIndex };
                        } else {
                            this._site.pressedEntity = { type: _UI.ObjectType.groupHeader, index: currentPressedHeaderIndex };
                        }

                        if (this._site.pressedEntity.index !== _Constants._INVALID_INDEX) {
                            this._site.pressedPosition = _ElementUtilities._getCursorPos(eventObject);

                            var allowed = site.verifySelectionAllowed(this._site.pressedEntity);
                            this._canSelect = allowed.canSelect;
                            this._canTapSelect = allowed.canTapSelect;

                            this._swipeBehaviorSelectionChanged = false;
                            this._selectionHint = null;

                            if (this._site.pressedEntity.type !== _UI.ObjectType.groupHeader) {
                                this._site.pressedItemBox = site.itemBoxAtIndex(this._site.pressedEntity.index);
                                this._site.pressedContainer = site.containerAtIndex(this._site.pressedEntity.index);
                                this._site.animatedElement = _BaseUtils.isPhone ? this._site.pressedItemBox : this._site.pressedContainer;
                                this._site.pressedHeader = null;
                                this._togglePressed(true, false, eventObject);
                                this._site.pressedContainer.addEventListener('dragstart', this._DragStartBound);
                                if (!touchInput) {
                                    // This only works for non touch input because on touch input we set capture which immediately fires the MSPointerOut.
                                    _ElementUtilities._addEventListener(this._site.pressedContainer, 'pointerenter', this._PointerEnterBound, false);
                                    _ElementUtilities._addEventListener(this._site.pressedContainer, 'pointerleave', this._PointerLeaveBound, false);
                                }
                            } else {
                                this._site.pressedHeader = this._site.headerFromElement(eventObject.target);
                                // Interactions with the headers on phone show an animation
                                if (_BaseUtils.isPhone) {
                                    this._site.animatedElement = this._site.pressedHeader;
                                    this._togglePressed(true, false, eventObject);
                                } else {
                                    this._site.pressedItemBox = null;
                                    this._site.pressedContainer = null;
                                    this._site.animatedElement = null;
                                }
                            }

                            if (!this._resetPointerDownStateBound) {
                                this._resetPointerDownStateBound = this._resetPointerDownStateForPointerId.bind(this);
                            }

                            if (!touchInput) {
                                _ElementUtilities._addEventListener(_Global, "pointerup", this._resetPointerDownStateBound, false);
                                _ElementUtilities._addEventListener(_Global, "pointercancel", this._resetPointerDownStateBound, false);
                            }

                            // The gesture recognizer is used for SRG, which is not supported on Phone
                            if (this._canSelect && !_BaseUtils.isPhone) {
                                if (!this._gestureRecognizer) {
                                    this._gestureRecognizer = this._createGestureRecognizer();
                                }
                                this._addSelectionHint();
                            }
                            this._pointerId = eventObject.pointerId;
                            this._pointerRightButton = rightButton;
                            this._pointerTriggeredSRG = false;

                            if (this._gestureRecognizer && touchInput) {
                                try {
                                    this._gestureRecognizer.addPointer(this._pointerId);
                                } catch (e) {
                                    this._gestureRecognizer.stop();
                                }
                            }
                        }
                    }

                    if (mustSetCapture) {
                        if (touchInput) {
                            try {
                                // Move pointer capture to avoid hover visual on second finger
                                _ElementUtilities._setPointerCapture(site.canvasProxy, eventObject.pointerId);
                            } catch (e) {
                                _WriteProfilerMark("WinJS.UI._ItemEventsHandler:MSPointerDown,StopTM");
                                return;
                            }
                        }
                    }

                    // Once the shift selection pivot is set, it remains the same until the user
                    // performs a left- or right-click without holding the shift key down.
                    if (this._site.pressedEntity.type !== _UI.ObjectType.groupHeader &&
                            this._selectionAllowed() && this._multiSelection() &&       // Multi selection enabled
                            this._site.pressedEntity.index !== _Constants._INVALID_INDEX &&    // A valid item was clicked
                            site.selection._getFocused().index !== _Constants._INVALID_INDEX && site.selection._pivot === _Constants._INVALID_INDEX) {
                        site.selection._pivot = site.selection._getFocused().index;
                    }

                    _WriteProfilerMark("WinJS.UI._ItemEventsHandler:MSPointerDown,StopTM");
                },

                onPointerEnter: function ItemEventsHandler_onPointerEnter(eventObject) {
                    if (this._site.pressedContainer && this._pointerId === eventObject.pointerId) {
                        this._togglePressed(true, false, eventObject);
                    }
                },

                onPointerLeave: function ItemEventsHandler_onPointerLeave(eventObject) {
                    if (this._site.pressedContainer && this._pointerId === eventObject.pointerId) {
                        this._togglePressed(false, true /* synchronous */, eventObject);
                    }
                },

                onDragStart: function ItemEventsHandler_onDragStart() {
                    this._resetPressedContainer();
                },

                _resetPressedContainer: function ItemEventsHandler_resetPressedContainer() {
                    if ((this._site.pressedContainer || this._site.pressedHeader) && this._site.animatedElement) {
                        this._togglePressed(false);
                        if (this._site.pressedContainer) {
                            this._site.pressedContainer.removeEventListener('dragstart', this._DragStartBound);
                            _ElementUtilities._removeEventListener(this._site.pressedContainer, 'pointerenter', this._PointerEnterBound, false);
                            _ElementUtilities._removeEventListener(this._site.pressedContainer, 'pointerleave', this._PointerLeaveBound, false);
                        }
                    }
                },

                onClick: function ItemEventsHandler_onClick(eventObject) {
                    if (!this._skipClick) {
                        // Handle the UIA invoke action on an item. this._skipClick is false which tells us that we received a click
                        // event without an associated MSPointerUp event. This means that the click event was triggered thru UIA
                        // rather than thru the GUI.
                        var entity = { type: _UI.ObjectType.item, index: this._site.indexForItemElement(eventObject.target) };
                        if (entity.index === _Constants._INVALID_INDEX) {
                            entity.index = this._site.indexForHeaderElement(eventObject.target);
                            if (entity.index !== _Constants._INVALID_INDEX) {
                                entity.type = _UI.ObjectType.groupHeader;
                            }
                        }

                        if (entity.index !== _Constants._INVALID_INDEX &&
                            (_ElementUtilities.hasClass(eventObject.target, this._site.accessibleItemClass) || _ElementUtilities.hasClass(eventObject.target, _Constants._headerClass))) {
                            var allowed = this._site.verifySelectionAllowed(entity);
                            if (allowed.canTapSelect) {
                                this.handleTap(entity);
                            }
                            this._site.fireInvokeEvent(entity, eventObject.target);
                        }
                    }
                },

                onPointerUp: function ItemEventsHandler_onPointerUp(eventObject) {
                    _WriteProfilerMark("WinJS.UI._ItemEventsHandler:MSPointerUp,StartTM");

                    var site = this._site;
                    this._skipClick = true;
                    var that = this;
                    var swipeEnabled = this._site.swipeBehavior === _UI.SwipeBehavior.select;
                    _BaseUtils._yieldForEvents(function () {
                        that._skipClick = false;
                    });

                    try {
                        // Release the pointer capture to allow in air touch pointers to be reused for multiple interactions
                        _ElementUtilities._releasePointerCapture(site.canvasProxy, eventObject.pointerId);
                    } catch (e) {
                        // This can throw if SeZo had capture or if the pointer was not already captured
                    }

                    var touchInput = (eventObject.pointerType === PT_TOUCH),
                        releasedElement = this._releasedElement(eventObject),
                        releasedIndex = site.indexForItemElement(releasedElement),
                        releasedHeaderIndex = releasedElement && _ElementUtilities.hasClass(releasedElement, _Constants._headerContainerClass) ? site.indexForHeaderElement(site.pressedHeader) : site.indexForHeaderElement(releasedElement);

                    if (this._pointerId === eventObject.pointerId) {
                        var releasedEntity;
                        if (releasedHeaderIndex === _Constants._INVALID_INDEX) {
                            releasedEntity = { type: _UI.ObjectType.item, index: releasedIndex };
                        } else {
                            releasedEntity = { type: _UI.ObjectType.groupHeader, index: releasedHeaderIndex };
                        }

                        this._resetPressedContainer();

                        if (this._site.pressedEntity.type !== _UI.ObjectType.groupHeader && releasedEntity.type !== _UI.ObjectType.groupHeader &&
                                this._site.pressedContainer && this._site.pressedEntity.index === releasedEntity.index) {

                            if (!eventObject.shiftKey) {
                                // Reset the shift selection pivot when the user clicks w/o pressing shift
                                site.selection._pivot = _Constants._INVALID_INDEX;
                            }

                            if (eventObject.shiftKey) {
                                // Shift selection should work when shift or shift+ctrl are depressed for both left- and right-click
                                if (this._selectionAllowed() && this._multiSelection() && site.selection._pivot !== _Constants._INVALID_INDEX) {
                                    var firstIndex = Math.min(this._site.pressedEntity.index, site.selection._pivot),
                                        lastIndex = Math.max(this._site.pressedEntity.index, site.selection._pivot),
                                        additive = (this._pointerRightButton || eventObject.ctrlKey || site.tapBehavior === _UI.TapBehavior.toggleSelect);
                                    site.selectRange(firstIndex, lastIndex, additive);
                                }
                            } else if (eventObject.ctrlKey || (this._selectionAllowed() && swipeEnabled && this._pointerRightButton)) {
                                // Swipe emulation
                                this.handleSwipeBehavior(this._site.pressedEntity.index);
                            }
                        }

                        if ((this._site.pressedHeader || this._site.pressedContainer) && this._swipeBehaviorState !== MSManipulationEventStates.MS_MANIPULATION_STATE_COMMITTED) {
                            var upPosition = _ElementUtilities._getCursorPos(eventObject);
                            var isTap = Math.abs(upPosition.left - this._site.pressedPosition.left) <= _Constants._TAP_END_THRESHOLD &&
                                Math.abs(upPosition.top - this._site.pressedPosition.top) <= _Constants._TAP_END_THRESHOLD;

                            this._endSelfRevealGesture();
                            this._clearItem(this._site.pressedEntity, this._isSelected(this._site.pressedEntity.index));

                            // We do not care whether or not the pressed and released indices are equivalent when the user is using touch. The only time they won't be is if the user
                            // tapped the edge of an item and the pressed animation shrank the item such that the user's finger was no longer over it. In this case, the item should
                            // be considered tapped.
                            // However, if the user is using touch then we must perform an extra check. Sometimes we receive MSPointerUp events when the user intended to pan or swipe.
                            // This extra check ensures that these intended pans/swipes aren't treated as taps.
                            if (!this._pointerRightButton && !this._pointerTriggeredSRG && !eventObject.ctrlKey && !eventObject.shiftKey &&
                                    ((touchInput && isTap) ||
                                    (!touchInput && this._site.pressedEntity.index === releasedEntity.index && this._site.pressedEntity.type === releasedEntity.type))) {
                                if (releasedEntity.type === _UI.ObjectType.groupHeader) {
                                    this._site.pressedHeader = site.headerAtIndex(releasedEntity.index);
                                    this._site.pressedItemBox = null;
                                    this._site.pressedContainer = null;
                                } else {
                                    this._site.pressedItemBox = site.itemBoxAtIndex(releasedEntity.index);
                                    this._site.pressedContainer = site.containerAtIndex(releasedEntity.index);
                                    this._site.pressedHeader = null;
                                }

                                if (this._canTapSelect) {
                                    this.handleTap(this._site.pressedEntity);
                                }
                                this._site.fireInvokeEvent(this._site.pressedEntity, this._site.pressedItemBox || this._site.pressedHeader);
                            }
                        }

                        if (this._site.pressedEntity.index !== _Constants._INVALID_INDEX) {
                            site.changeFocus(this._site.pressedEntity, true, false, true);
                        }

                        this.resetPointerDownState();
                    }

                    _WriteProfilerMark("WinJS.UI._ItemEventsHandler:MSPointerUp,StopTM");
                },

                onPointerCancel: function ItemEventsHandler_onPointerCancel(eventObject) {
                    if (this._pointerId === eventObject.pointerId && this._swipeBehaviorState !== MSManipulationEventStates.MS_MANIPULATION_STATE_PRESELECT) {
                        _WriteProfilerMark("WinJS.UI._ItemEventsHandler:MSPointerCancel,info");
                        this.resetPointerDownState();
                    }
                },

                onLostPointerCapture: function ItemEventsHandler_onLostPointerCapture(eventObject) {
                    if (this._pointerId === eventObject.pointerId && this._swipeBehaviorState !== MSManipulationEventStates.MS_MANIPULATION_STATE_PRESELECT) {
                        _WriteProfilerMark("WinJS.UI._ItemEventsHandler:MSLostPointerCapture,info");
                        this.resetPointerDownState();
                    }
                },

                // In order for the control to play nicely with other UI controls such as the app bar, it calls preventDefault on
                // contextmenu events. It does this only when selection is enabled, the event occurred on or within an item, and
                // the event did not occur on an interactive element.
                onContextMenu: function ItemEventsHandler_onContextMenu(eventObject) {
                    var containerElement = this._site.containerFromElement(eventObject.target);

                    if (this._selectionAllowed() && containerElement && !this._isInteractive(eventObject.target)) {
                        eventObject.preventDefault();
                    }
                },

                onMSHoldVisual: function ItemEventsHandler_onMSHoldVisual(eventObject) {
                    if (!this._isInteractive(eventObject.target)) {
                        eventObject.preventDefault();
                    }
                },

                onDataChanged: function ItemEventsHandler_onDataChanged() {
                    this.resetPointerDownState();
                },

                handleSwipeBehavior: function ItemEventsHandler_handleSwipeBehavior(itemIndex) {
                    if (this._selectionAllowed(itemIndex)) {
                        this._toggleItemSelection(itemIndex);
                    }
                },

                handleTap: function ItemEventsHandler_handleTap(entity) {
                    if (entity.type === _UI.ObjectType.groupHeader) {
                        return;
                    }

                    var site = this._site,
                        selection = site.selection;

                    if (this._selectionAllowed(entity.index) && this._selectOnTap()) {
                        if (site.tapBehavior === _UI.TapBehavior.toggleSelect) {
                            this._toggleItemSelection(entity.index);
                        } else {
                            // site.tapBehavior === _UI.TapBehavior.directSelect so ensure only itemIndex is selected
                            if (site.selectionMode === _UI.SelectionMode.multi || !selection._isIncluded(entity.index)) {
                                selection.set(entity.index);
                            }
                        }
                    }
                },

                // In single selection mode, in addition to itemIndex's selection state being toggled,
                // all other items will become deselected
                _toggleItemSelection: function ItemEventsHandler_toggleItemSelection(itemIndex) {
                    var site = this._site,
                        selection = site.selection,
                        selected = selection._isIncluded(itemIndex);

                    if (site.selectionMode === _UI.SelectionMode.single) {
                        if (!selected) {
                            selection.set(itemIndex);
                        } else {
                            selection.clear();
                        }
                    } else {
                        if (!selected) {
                            selection.add(itemIndex);
                        } else {
                            selection.remove(itemIndex);
                        }
                    }
                },

                _getCurrentPoint: function ItemEventsHandler_getCurrentPoint(eventObject) {
                    return _WinRT.Windows.UI.Input.PointerPoint.getCurrentPoint(eventObject.pointerId);
                },

                _containedInElementWithClass: function ItemEventsHandler_containedInElementWithClass(element, className) {
                    if (element.parentNode) {
                        var matches = element.parentNode.querySelectorAll("." + className + ", ." + className + " *");
                        for (var i = 0, len = matches.length; i < len; i++) {
                            if (matches[i] === element) {
                                return true;
                            }
                        }
                    }
                    return false;
                },

                _isSelected: function ItemEventsHandler_isSelected(index) {
                    return (!this._swipeBehaviorSelectionChanged && this._site.selection._isIncluded(index)) || (this._swipeBehaviorSelectionChanged && this.swipeBehaviorSelected);
                },

                _isInteractive: function ItemEventsHandler_isInteractive(element) {
                    return this._containedInElementWithClass(element, "win-interactive");
                },

                _togglePressed: function ItemEventsHandler_togglePressed(add, synchronous, eventObject) {
                    var that = this;
                    var isHeader = this._site.pressedEntity.type === _UI.ObjectType.groupHeader;

                    this._site.animatedDownPromise && this._site.animatedDownPromise.cancel();

                    if (_BaseUtils.isPhone && !isHeader && _ElementUtilities.hasClass(this._site.pressedItemBox, _Constants._nonSelectableClass)) {
                        return;
                    }

                    if (!this._staticMode(isHeader)) {
                        if (add) {
                            if (!_ElementUtilities.hasClass(this._site.animatedElement, _Constants._pressedClass)) {
                                _WriteProfilerMark("WinJS.UI._ItemEventsHandler:applyPressedUI,info");
                                _ElementUtilities.addClass(this._site.animatedElement, _Constants._pressedClass);

                                if (eventObject && _BaseUtils.isPhone) {
                                    var boundingElement = isHeader ? that._site.pressedHeader : that._site.pressedContainer;
                                    var transform = tiltTransform(eventObject.clientX, eventObject.clientY, boundingElement.getBoundingClientRect());
                                    // Timeout prevents item from looking like it was pressed down during swipes and pans
                                    this._site.animatedDownPromise = Promise.timeout(50).then(function () {
                                        applyDownVisual(transform);
                                    });
                                } else {
                                    // Shrink by 97.5% unless that is larger than 7px in either direction. In that case we cap the
                                    // scale so that it is no larger than 7px in either direction. We keep the scale uniform in both x
                                    // and y directions. Note that this scale cap only works if getItemPosition returns synchronously
                                    // which it does for the built in layouts.
                                    var scale = 0.975;
                                    var maxPixelsToShrink = 7;

                                    this._site.getItemPosition(this._site.pressedEntity).then(function (pos) {
                                        if (pos.contentWidth > 0) {
                                            scale = Math.max(scale, (1 - (maxPixelsToShrink / pos.contentWidth)));
                                        }
                                        if (pos.contentHeight > 0) {
                                            scale = Math.max(scale, (1 - (maxPixelsToShrink / pos.contentHeight)));
                                        }
                                    }, function () {
                                        // Swallow errors in case data source changes
                                    });
                                    applyDownVisual("scale(" + scale + "," + scale + ")");
                                }
                            }
                        } else {
                            if (_ElementUtilities.hasClass(this._site.animatedElement, _Constants._pressedClass)) {
                                var element = this._site.animatedElement;
                                var expectingStyle = this._site.animatedElementScaleTransform;
                                if (synchronous) {
                                    applyUpVisual(element, expectingStyle);
                                } else {
                                    // Force removal of the _pressedClass to be asynchronous so that users will see at
                                    // least one frame of the shrunken item when doing a quick tap.
                                    //
                                    // setImmediate is used rather than requestAnimationFrame to ensure that the item
                                    // doesn't get stuck down for too long -- apps are told to put long running invoke
                                    // code behind a setImmediate and togglePressed's async code needs to run first.
                                    _BaseUtils._setImmediate(function () {
                                        if (_ElementUtilities.hasClass(element, _Constants._pressedClass)) {
                                            applyUpVisual(element, expectingStyle);
                                        }
                                    });
                                }
                            }
                        }
                    }

                    function applyDownVisual(transform) {
                        if (that._site.animatedElement.style[transformNames.scriptName] === "") {
                            that._site.animatedElement.style[transformNames.scriptName] = transform;
                            that._site.animatedElementScaleTransform = that._site.animatedElement.style[transformNames.scriptName];
                        } else {
                            that._site.animatedElementScaleTransform = "";
                        }
                    }

                    function applyUpVisual(element, expectingStyle) {
                        _WriteProfilerMark("WinJS.UI._ItemEventsHandler:removePressedUI,info");
                        _ElementUtilities.removeClass(element, _Constants._pressedClass);
                        if (_BaseUtils.isPhone) {
                            if (that._containsTransform(element, expectingStyle)) {
                                _TransitionAnimation.executeTransition(element, {
                                    property: transformNames.cssName,
                                    delay: 0,
                                    duration: 500,
                                    timing: "cubic-bezier(0.7025,0,0.9225,-0.115)",
                                    to: element.style[transformNames.scriptName].replace(expectingStyle, "")
                                });
                            }
                        } else {
                            that._removeTransform(element, expectingStyle);
                        }
                    }
                },

                _containsTransform: function ItemEventsHandler_containsTransform(element, transform) {
                    return transform && element.style[transformNames.scriptName].indexOf(transform) !== -1;
                },

                _removeTransform: function ItemEventsHandler_removeTransform(element, transform) {
                    if (this._containsTransform(element, transform)) {
                        element.style[transformNames.scriptName] = element.style[transformNames.scriptName].replace(transform, "");
                    }
                },

                _endSwipeBehavior: function ItemEventsHandler_endSwipeBehavior() {
                    if (!(this._swipeBehaviorState === MSManipulationEventStates.MS_MANIPULATION_STATE_PRESELECT ||
                        this._swipeBehaviorState === MSManipulationEventStates.MS_MANIPULATION_STATE_SELECTING ||
                        this._swipeBehaviorState === MSManipulationEventStates.MS_MANIPULATION_STATE_DRAGGING ||
                        this._swipeBehaviorState === MSManipulationEventStates.MS_MANIPULATION_STATE_COMMITTED ||
                        this._swipeBehaviorState === MSManipulationEventStates.MS_MANIPULATION_STATE_CANCELLED)) {
                        return;
                    }

                    if (this._site.pressedEntity.type === _UI.ObjectType.groupHeader) {
                        return;
                    }

                    this._flushUIBatches();
                    var selectionHint = this._selectionHint;
                    this._selectionHint = null;

                    if (this._site.pressedItemBox) {
                        var pressedIndex = this._site.pressedEntity.index,
                            selected = this._site.selection._isIncluded(pressedIndex);
                        if (selected) {
                            var elementsToShowHide = _ElementUtilities._getElementsByClasses(this._site.pressedItemBox, [_Constants._selectionCheckmarkClass, _Constants._selectionCheckmarkBackgroundClass]);
                            for (var i = 0; i < elementsToShowHide.length; i++) {
                                elementsToShowHide[i].style.opacity = 1;
                            }
                        }
                        this._clearItem(this._site.pressedEntity, selected);
                        if (selectionHint) {
                            this._removeSelectionHint(selectionHint);
                        }
                        delete this._animations[pressedIndex];
                    }
                },

                _createGestureRecognizer: function ItemEventsHandler_createGestureRecognizer() {
                    var rootElement = this._site.eventHandlerRoot;
                    var recognizer = _ElementUtilities._createGestureRecognizer();
                    recognizer.target = rootElement;
                    var that = this;
                    rootElement.addEventListener("MSGestureHold", function (eventObject) {
                        if (that._site.pressedEntity.index !== -1 && eventObject.detail === _ElementUtilities._MSGestureEvent.MSGESTURE_FLAG_BEGIN) {
                            that._startSelfRevealGesture();
                        }
                    });
                    return recognizer;
                },

                _dispatchSwipeBehavior: function ItemEventsHandler_dispatchSwipeBehavior(manipulationState) {
                    if (this._site.pressedEntity.type === _UI.ObjectType.groupHeader ||
                        this._site.swipeBehavior !== _UI.SwipeBehavior.select) {
                        return;
                    }
                    this._site.selection._pivot = _Constants._INVALID_INDEX;
                    if (this._site.pressedItemBox) {
                        var pressedIndex = this._site.pressedEntity.index;
                        if (this._swipeBehaviorState !== manipulationState) {
                            if (manipulationState === MSManipulationEventStates.MS_MANIPULATION_STATE_DRAGGING && this._canSelect) {
                                this._animateSelectionChange(this._site.selection._isIncluded(pressedIndex));
                                this._removeSelectionHint(this._selectionHint);
                            } else if (manipulationState === MSManipulationEventStates.MS_MANIPULATION_STATE_PRESELECT) {
                                _WriteProfilerMark("WinJS.UI._ItemEventsHandler:crossSlidingStarted,info");
                                var site = this._site,
                                    pressedElement = site.itemAtIndex(pressedIndex),
                                    selected = site.selection._isIncluded(pressedIndex);

                                if (this._selfRevealGesture) {
                                    this._selfRevealGesture.finishAnimation();
                                    this._selfRevealGesture = null;
                                } else if (this._canSelect) {
                                    this._prepareItem(this._site.pressedEntity, pressedElement, selected);
                                }

                                if (this._swipeBehaviorState !== MSManipulationEventStates.MS_MANIPULATION_STATE_SELECTING) {
                                    if (this._site.animatedElement && _ElementUtilities.hasClass(this._site.animatedElement, _Constants._pressedClass)) {
                                        this._site.animatedDownPromise && this._site.animatedDownPromise.cancel();
                                        _ElementUtilities.removeClass(this._site.animatedElement, _Constants._pressedClass);
                                        this._removeTransform(this._site.animatedElement, this._site.animatedElementScaleTransform);
                                    }

                                    this._showSelectionHintCheckmark();
                                } else {
                                    this._animateSelectionChange(this._site.selection._isIncluded(pressedIndex));
                                }
                            } else if (manipulationState === MSManipulationEventStates.MS_MANIPULATION_STATE_COMMITTED) {
                                _WriteProfilerMark("WinJS.UI._ItemEventsHandler:crossSlidingCompleted,info");
                                var site = this._site,
                                    selection = site.selection,
                                    swipeBehaviorSelectionChanged = this._swipeBehaviorSelectionChanged,
                                    swipeBehaviorSelected = this.swipeBehaviorSelected;

                                if (this._swipeBehaviorState === MSManipulationEventStates.MS_MANIPULATION_STATE_SELECTING && swipeBehaviorSelectionChanged) {
                                    if (this._selectionAllowed() && site.swipeBehavior === _UI.SwipeBehavior.select) {
                                        if (site.selectionMode === _UI.SelectionMode.single) {
                                            if (swipeBehaviorSelected) {
                                                selection.set(pressedIndex);
                                            } else if (selection._isIncluded(pressedIndex)) {
                                                selection.remove(pressedIndex);
                                            }
                                        } else {
                                            if (swipeBehaviorSelected) {
                                                selection.add(pressedIndex);
                                            } else if (selection._isIncluded(pressedIndex)) {
                                                selection.remove(pressedIndex);
                                            }
                                        }
                                    }
                                }

                                // snap back and remove addional elements
                                this._endSwipeBehavior();
                            } else if (manipulationState === MSManipulationEventStates.MS_MANIPULATION_STATE_SELECTING && this._canSelect) {
                                this._animateSelectionChange(!this._site.selection._isIncluded(pressedIndex));
                            } else if (this._swipeBehaviorState === MSManipulationEventStates.MS_MANIPULATION_STATE_SELECTING && this._canSelect) {
                                this._animateSelectionChange(this._site.selection._isIncluded(pressedIndex), (manipulationState === MSManipulationEventStates.MS_MANIPULATION_STATE_CANCELLED));
                            }
                        }
                    }

                    this._swipeBehaviorState = manipulationState;
                },


                _resetPointerDownStateForPointerId: function ItemEventsHandler_resetPointerDownState(eventObject) {
                    if (this._pointerId === eventObject.pointerId) {
                        this.resetPointerDownState();
                    }
                },

                resetPointerDownState: function ItemEventsHandler_resetPointerDownState() {
                    if (this._gestureRecognizer) {
                        this._endSelfRevealGesture();
                        this._endSwipeBehavior();
                    }
                    this._site.pressedElement = null;
                    _ElementUtilities._removeEventListener(_Global, "pointerup", this._resetPointerDownStateBound);
                    _ElementUtilities._removeEventListener(_Global, "pointercancel", this._resetPointerDownStateBound);

                    this._resetPressedContainer();

                    this._site.pressedContainer = null;
                    this._site.animatedElement = null;
                    this._site.pressedHeader = null;
                    this._site.pressedItemBox = null;

                    this._removeSelectionHint(this._selectionHint);
                    this._selectionHint = null;

                    this._site.pressedEntity = { type: _UI.ObjectType.item, index: _Constants._INVALID_INDEX };
                    this._pointerId = null;
                },

                // Play the self-reveal gesture (SRG) animation which jiggles the item to reveal the selection hint behind it.
                // This function is overridden by internal teams to add a tooltip on SRG start - treat this function as a public API for the sake of function name/parameter changes.
                _startSelfRevealGesture: function ItemEventsHandler_startSelfRevealGesture() {
                    if (this._canSelect && this._site.swipeBehavior === _UI.SwipeBehavior.select) {
                        _WriteProfilerMark("WinJS.UI._ItemEventsHandler:playSelfRevealGesture,info");

                        var that = this;
                        var site = this._site,
                            index = this._site.pressedEntity.index,
                            itemBox = site.itemBoxAtIndex(index),
                            selected = site.selection._isIncluded(index),
                            finished = false;

                        var swipeReveal = function () {
                            var top,
                                left;

                            if (site.horizontal) {
                                top = _Constants._VERTICAL_SWIPE_SELF_REVEAL_GESTURE + "px";
                                left = "0px";
                            } else {
                                top = "0px";
                                left = (site.rtl() ? "" : "-") + _Constants._HORIZONTAL_SWIPE_SELF_REVEAL_GESTURE + "px";
                            }

                            return Animations.swipeReveal(itemBox, { top: top, left: left });
                        };

                        var swipeHide = function () {
                            return finished ? Promise.wrap() : Animations.swipeReveal(itemBox, { top: "0px", left: "0px" });
                        };

                        var cleanUp = function (selectionHint) {
                            if (!site.isZombie()) {
                                if (selectionHint) {
                                    that._removeSelectionHint(selectionHint);
                                }
                                that._clearItem(site.pressedEntity, site.selection._isIncluded(index));
                            }
                        };

                        // Immediately begins the last phase of the SRG animation which animates the item back to its original location
                        var finishAnimation = function () {
                            that._selfRevealGesture._promise.cancel();
                            finished = true;
                            var selectionHint = that._selectionHint;
                            that._selectionHint = null;
                            return swipeHide().then(function () {
                                itemBox.style[transformNames.scriptName] = "";
                                cleanUp(selectionHint);
                            });
                        };

                        this._prepareItem(this._site.pressedEntity, itemBox, selected);
                        this._showSelectionHintCheckmark();

                        this._pointerTriggeredSRG = true;
                        this._selfRevealGesture = {
                            finishAnimation: finishAnimation,
                            _promise: swipeReveal().
                                then(swipeHide).
                                then(function () {
                                    if (!finished) {
                                        that._hideSelectionHintCheckmark();
                                        cleanUp();
                                        that._selfRevealGesture = null;
                                    }
                                })
                        };
                    }
                },

                // This function is overridden by internal teams to remove a tooltip on SRG completion - treat this function as a public API for the sake of function name/parameter changes
                _endSelfRevealGesture: function ItemEventsHandler_endSelfRevealGesture() {
                    if (this._selfRevealGesture) {
                        this._selfRevealGesture.finishAnimation();
                        this._selfRevealGesture = null;
                    }
                },

                _prepareItem: function ItemEventsHandler_prepareItem(pressedEntity, pressedElement, selected) {
                    if (pressedEntity.type === _UI.ObjectType.groupHeader) {
                        return;
                    }

                    var that = this,
                        site = this._site,
                        pressedIndex = pressedEntity.index;

                    function addSwipeClass(container) {
                        if (!that._swipeClassTracker[uniqueID(container)]) {
                            _ElementUtilities.addClass(container, _Constants._swipeClass);
                            that._swipeClassTracker[uniqueID(container)] = 1;
                        } else {
                            that._swipeClassTracker[uniqueID(container)]++;
                        }
                    }

                    if (!selected) {
                        (this._animations[pressedIndex] || Promise.wrap()).then(function () {
                            if (!site.isZombie() && pressedEntity.type !== _UI.ObjectType.groupHeader && site.pressedEntity.index !== -1) {
                                pressedIndex = site.pressedEntity.index;

                                var pressedElement = site.itemAtIndex(pressedIndex),
                                    itemBox = site.itemBoxAtIndex(pressedIndex),
                                    container = site.containerAtIndex(pressedIndex);

                                addSwipeClass(container);

                                if (!_ElementUtilities._isSelectionRendered(itemBox)) {
                                    ItemEventsHandler.renderSelection(itemBox, pressedElement, true, container);

                                    _ElementUtilities.removeClass(itemBox, _Constants._selectedClass);
                                    _ElementUtilities.removeClass(container, _Constants._selectedClass);

                                    var nodes = itemBox.querySelectorAll(_ElementUtilities._selectionPartsSelector);
                                    for (var i = 0, len = nodes.length; i < len; i++) {
                                        nodes[i].style.opacity = 0;
                                    }
                                }
                            }
                        });
                    } else {
                        var container = site.containerAtIndex(pressedIndex);
                        addSwipeClass(container);
                    }
                },

                _clearItem: function ItemEventsHandler_clearItem(pressedEntity, selected) {
                    if (pressedEntity.type !== _UI.ObjectType.item) {
                        return;
                    }

                    var that = this,
                        site = this._site,
                        container = site.containerAtIndex(pressedEntity.index),
                        itemBox = site.itemBoxAtIndex(pressedEntity.index),
                        element = site.itemAtIndex(pressedEntity.index);

                    function removeSwipeClass(container) {
                        var refCount = --that._swipeClassTracker[uniqueID(container)];
                        if (!refCount) {
                            delete that._swipeClassTracker[uniqueID(container)];
                            _ElementUtilities.removeClass(container, _Constants._swipeClass);
                            return true;
                        }
                        return false;
                    }

                    function removeSwipeFromItemsBlock(container) {
                        var itemsBlock = container.parentNode;
                        if (itemsBlock && _ElementUtilities.hasClass(itemsBlock, _Constants._itemsBlockClass)) {
                            removeSwipeClass(itemsBlock);
                        }
                    }

                    if (container && itemBox && element) {
                        var doneSwiping = removeSwipeClass(container);
                        removeSwipeFromItemsBlock(container);
                        if (doneSwiping) {
                            ItemEventsHandler.renderSelection(itemBox, element, selected, true, container);
                        }
                    }
                },

                _animateSelectionChange: function ItemEventsHandler_animateSelectionChange(select, includeCheckmark) {
                    var that = this,
                        pressedContainer = this._site.pressedContainer,
                        pressedItemBox = this._site.pressedItemBox;

                    function toggleClasses() {
                        var classOperation = select ? "addClass" : "removeClass";
                        _ElementUtilities[classOperation](pressedItemBox, _Constants._selectedClass);
                        _ElementUtilities[classOperation](pressedContainer, _Constants._selectedClass);
                        if (that._selectionHint) {
                            var hintCheckMark = getElementWithClass(that._selectionHint, _Constants._selectionHintClass);
                            if (hintCheckMark) {
                                _ElementUtilities[classOperation](hintCheckMark, _Constants._revealedClass);
                            }
                        }
                    }

                    this._swipeBehaviorSelectionChanged = true;
                    this.swipeBehaviorSelected = select;

                    var elementsToShowHide = _ElementUtilities._getElementsByClasses(this._site.pressedItemBox, [_Constants._selectionBorderClass, _Constants._selectionBackgroundClass]);

                    if (!select || includeCheckmark) {
                        elementsToShowHide = elementsToShowHide.concat(_ElementUtilities._getElementsByClasses(this._site.pressedItemBox, [_Constants._selectionCheckmarkBackgroundClass, _Constants._selectionCheckmarkClass]));
                    }

                    _WriteProfilerMark("WinJS.UI._ItemEventsHandler:" + (select ? "hitSelectThreshold" : "hitUnselectThreshold") + ",info");

                    this._applyUIInBatches(function () {
                        _WriteProfilerMark("WinJS.UI._ItemEventsHandler:" + (select ? "apply" : "remove") + "SelectionVisual,info");
                        var opacity = (select ? 1 : 0);
                        for (var i = 0; i < elementsToShowHide.length; i++) {
                            elementsToShowHide[i].style.opacity = opacity;
                        }

                        toggleClasses();
                    });
                },

                _showSelectionHintCheckmark: function ItemEventsHandler_showSelectionHintCheckmark() {
                    if (this._selectionHint) {
                        var hintCheckMark = getElementWithClass(this._selectionHint, _Constants._selectionHintClass);
                        if (hintCheckMark) {
                            hintCheckMark.style.display = 'block';
                        }
                    }
                },

                _hideSelectionHintCheckmark: function ItemEventsHandler_hideSelectionHintCheckmark() {
                    if (this._selectionHint) {
                        var hintCheckMark = getElementWithClass(this._selectionHint, _Constants._selectionHintClass);
                        if (hintCheckMark) {
                            hintCheckMark.style.display = 'none';
                        }
                    }
                },

                _addSelectionHint: function ItemEventsHandler_addSelectionHint() {
                    if (this._site.pressedEntity.type === _UI.ObjectType.groupHeader) {
                        return;
                    }

                    var selectionHint,
                        site = this._site;

                    if (site.customFootprintParent) {
                        selectionHint = this._selectionHint = _Global.document.createElement("div");
                        selectionHint.className = _Constants._containerClass;

                        var that = this;
                        site.getItemPosition(this._site.pressedEntity).then(function (pos) {
                            if (!site.isZombie() && that._selectionHint && that._selectionHint === selectionHint) {
                                var style = selectionHint.style;
                                var cssText = ";position:absolute;" +
                                    (site.rtl() ? "right:" : "left:") + pos.left + "px;top:" +
                                    pos.top + "px;width:" + pos.contentWidth + "px;height:" + pos.contentHeight + "px";
                                style.cssText += cssText;
                                site.customFootprintParent.insertBefore(that._selectionHint, that._site.pressedItemBox);
                            }
                        }, function () {
                            // Swallow errors in case data source changes
                        });
                    } else {
                        selectionHint = this._selectionHint = this._site.pressedContainer;
                    }

                    if (!this._selectionHintTracker[uniqueID(selectionHint)]) {
                        _ElementUtilities.addClass(selectionHint, _Constants._footprintClass);

                        if (!site.selection._isIncluded(this._site.pressedEntity.index)) {
                            var element = _Global.document.createElement("div");
                            element.className = _Constants._selectionHintClass;
                            element.textContent = _Constants._SELECTION_CHECKMARK;
                            element.style.display = 'none';
                            this._selectionHint.insertBefore(element, this._selectionHint.firstElementChild);
                        }

                        this._selectionHintTracker[uniqueID(selectionHint)] = 1;
                    } else {
                        this._selectionHintTracker[uniqueID(selectionHint)]++;
                    }
                },

                _removeSelectionHint: function ItemEventsHandler_removeSelectionHint(selectionHint) {
                    if (selectionHint) {
                        var refCount = --this._selectionHintTracker[uniqueID(selectionHint)];
                        if (!refCount) {
                            delete this._selectionHintTracker[uniqueID(selectionHint)];

                            if (!this._site.customFootprintParent) {
                                _ElementUtilities.removeClass(selectionHint, _Constants._footprintClass);
                                var hintCheckMark = getElementWithClass(selectionHint, _Constants._selectionHintClass);
                                if (hintCheckMark) {
                                    hintCheckMark.parentNode.removeChild(hintCheckMark);
                                }
                            } else if (selectionHint.parentNode) {
                                selectionHint.parentNode.removeChild(selectionHint);
                            }
                        }
                    }
                },

                _releasedElement: function ItemEventsHandler_releasedElement(eventObject) {
                    return _Global.document.elementFromPoint(eventObject.clientX, eventObject.clientY);
                },

                _applyUIInBatches: function ItemEventsHandler_applyUIInBatches(work) {
                    var that = this;
                    this._work.push(work);

                    if (!this._paintedThisFrame) {
                        applyUI();
                    }

                    function applyUI() {
                        if (that._work.length > 0) {
                            that._flushUIBatches();
                            that._paintedThisFrame = _BaseUtils._requestAnimationFrame(applyUI.bind(that));
                        } else {
                            that._paintedThisFrame = null;
                        }
                    }
                },

                _flushUIBatches: function ItemEventsHandler_flushUIBatches() {
                    if (this._work.length > 0) {
                        var workItems = this._work;
                        this._work = [];

                        for (var i = 0; i < workItems.length; i++) {
                            workItems[i]();
                        }
                    }
                },

                _selectionAllowed: function ItemEventsHandler_selectionAllowed(itemIndex) {
                    var item = (itemIndex !== undefined ? this._site.itemAtIndex(itemIndex) : null),
                        itemSelectable = !(item && _ElementUtilities.hasClass(item, _Constants._nonSelectableClass));
                    return itemSelectable && this._site.selectionMode !== _UI.SelectionMode.none;
                },

                _multiSelection: function ItemEventsHandler_multiSelection() {
                    return this._site.selectionMode === _UI.SelectionMode.multi;
                },

                _selectOnTap: function ItemEventsHandler_selectOnTap() {
                    return this._site.tapBehavior === _UI.TapBehavior.toggleSelect || this._site.tapBehavior === _UI.TapBehavior.directSelect;
                },

                _staticMode: function ItemEventsHandler_staticMode(isHeader) {
                    if (isHeader) {
                        return this._site.headerTapBehavior === _UI.GroupHeaderTapBehavior.none;
                    } else {
                        return this._site.tapBehavior === _UI.TapBehavior.none && this._site.selectionMode === _UI.SelectionMode.none;
                    }
                },
            }, {
                // Avoids unnecessary UIA selection events by only updating aria-selected if it has changed
                setAriaSelected: function ItemEventsHandler_setAriaSelected(itemElement, isSelected) {
                    var ariaSelected = (itemElement.getAttribute("aria-selected") === "true");

                    if (isSelected !== ariaSelected) {
                        itemElement.setAttribute("aria-selected", isSelected);
                    }
                },

                renderSelection: function ItemEventsHandler_renderSelection(itemBox, element, selected, aria, container) {
                    if (!ItemEventsHandler._selectionTemplate) {
                        ItemEventsHandler._selectionTemplate = [];
                        ItemEventsHandler._selectionTemplate.push(createNodeWithClass(_Constants._selectionBackgroundClass));
                        ItemEventsHandler._selectionTemplate.push(createNodeWithClass(_Constants._selectionBorderClass));
                        ItemEventsHandler._selectionTemplate.push(createNodeWithClass(_Constants._selectionCheckmarkBackgroundClass));
                        var checkmark = createNodeWithClass(_Constants._selectionCheckmarkClass);
                        checkmark.textContent = _Constants._SELECTION_CHECKMARK;
                        ItemEventsHandler._selectionTemplate.push(checkmark);
                    }

                    // Update the selection rendering if necessary
                    if (selected !== _ElementUtilities._isSelectionRendered(itemBox)) {
                        if (selected) {
                            itemBox.insertBefore(ItemEventsHandler._selectionTemplate[0].cloneNode(true), itemBox.firstElementChild);

                            for (var i = 1, len = ItemEventsHandler._selectionTemplate.length; i < len; i++) {
                                itemBox.appendChild(ItemEventsHandler._selectionTemplate[i].cloneNode(true));
                            }
                        } else {
                            var nodes = itemBox.querySelectorAll(_ElementUtilities._selectionPartsSelector);
                            for (var i = 0, len = nodes.length; i < len; i++) {
                                itemBox.removeChild(nodes[i]);
                            }
                        }

                        _ElementUtilities[selected ? "addClass" : "removeClass"](itemBox, _Constants._selectedClass);
                        if (container) {
                            _ElementUtilities[selected ? "addClass" : "removeClass"](container, _Constants._selectedClass);
                        }
                    }

                    // To allow itemPropertyChange to work properly, aria needs to be updated after the selection visuals are added to the itemBox
                    if (aria) {
                        ItemEventsHandler.setAriaSelected(element, selected);
                    }
                },
            });

            return ItemEventsHandler;
        })

    });

});
// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/ListView/_SelectionManager',[
    'exports',
    '../../Core/_Global',
    '../../Core/_Base',
    '../../Promise',
    '../../_Signal',
    '../../Utilities/_UI',
    '../ItemContainer/_Constants'
    ], function selectionManagerInit(exports, _Global, _Base, Promise, _Signal, _UI, _Constants) {
    "use strict";

    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {
        _ItemSet: _Base.Namespace._lazy(function () {
            var _ItemSet = _Base.Class.define(function _ItemSet_ctor(listView, ranges, count) {
                this._listView = listView;
                this._ranges = ranges;
                this._itemsCount = count;
            });
            _ItemSet.prototype = {
                getRanges: function () {
                    var ranges = [];
                    for (var i = 0, len = this._ranges.length; i < len; i++) {
                        var range = this._ranges[i];
                        ranges.push({
                            firstIndex: range.firstIndex,
                            lastIndex: range.lastIndex,
                            firstKey: range.firstKey,
                            lastKey: range.lastKey
                        });
                    }
                    return ranges;
                },

                getItems: function () {
                    return exports.getItemsFromRanges(this._listView._itemsManager.dataSource, this._ranges);
                },

                isEverything: function () {
                    return this.count() === this._itemsCount;
                },

                count: function () {
                    var count = 0;
                    for (var i = 0, len = this._ranges.length; i < len; i++) {
                        var range = this._ranges[i];
                        count += range.lastIndex - range.firstIndex + 1;
                    }
                    return count;
                },

                getIndices: function () {
                    var indices = [];
                    for (var i = 0, len = this._ranges.length; i < len; i++) {
                        var range = this._ranges[i];
                        for (var n = range.firstIndex; n <= range.lastIndex; n++) {
                            indices.push(n);
                        }
                    }
                    return indices;
                }
            };
            return _ItemSet;
        }),

        getItemsFromRanges: function (dataSource, ranges) {
            var listBinding = dataSource.createListBinding(),
                promises = [];

            function getIndices() {
                var indices = [];
                for (var i = 0, len = ranges.length; i < len; i++) {
                    var range = ranges[i];
                    for (var j = range.firstIndex; j <= range.lastIndex; j++) {
                        indices.push(j);
                    }
                }
                return Promise.wrap(indices);
            }

            return getIndices().then(function (indices) {
                for (var i = 0; i < indices.length; i++) {
                    promises.push(listBinding.fromIndex(indices[i]));
                }

                return Promise.join(promises).then(function (items) {
                    listBinding.release();
                    return items;
                });
            });
        },

        _Selection: _Base.Namespace._lazy(function () {
            function isEverythingRange(ranges) {
                return ranges && ranges.firstIndex === 0 && ranges.lastIndex === Number.MAX_VALUE;
            }

            return _Base.Class.derive(exports._ItemSet, function (listView, indexesAndRanges) {
                this._listView = listView;
                this._itemsCount = -1;
                this._ranges = [];
                if (indexesAndRanges) {
                    this.set(indexesAndRanges);
                }
            }, {
                clear: function () {
                    /// <signature helpKeyword="WinJS.UI._Selection.prototype.clear">
                    /// <summary locid="WinJS.UI._Selection.prototype.clear">
                    /// Clears the selection.
                    /// </summary>
                    /// <returns type="Promise" locid="WinJS.UI._Selection.prototype.clear_returnValue">
                    /// A Promise that is fulfilled when the clear operation completes.
                    /// </returns>
                    /// </signature>

                    this._releaseRanges(this._ranges);
                    this._ranges = [];
                    return Promise.wrap();
                },

                set: function (items) {
                    /// <signature helpKeyword="WinJS.UI._Selection.prototype.set">
                    /// <summary locid="WinJS.UI._Selection.prototype.set">
                    /// Clears the current selection and replaces it with the specified items.
                    /// </summary>
                    /// <param name="items" locid="WinJS.UI._Selection.prototype.set_items">
                    /// The indexes or keys of the items that make up the selection.
                    /// You can provide different types of objects for the items parameter:
                    /// you can specify an index, a key, or a range of indexes.
                    /// It can also be an array that contains one or more of these objects.
                    /// </param>
                    /// <returns type="Promise" locid="WinJS.UI._Selection.prototype.set_returnValue">
                    /// A Promise that is fulfilled when the operation completes.
                    /// </returns>
                    /// </signature>

                    // A range with lastIndex set to Number.MAX_VALUE used to mean selectAll. Passing such range to set was equivalent to selectAll. This code preserves this behavior.
                    if (!isEverythingRange(items)) {
                        this._releaseRanges(this._ranges);
                        this._ranges = [];

                        var that = this;
                        return this._execute("_set", items).then(function () {
                            that._ranges.sort(function (left, right) {
                                return left.firstIndex - right.firstIndex;
                            });
                            return that._ensureKeys();
                        }).then(function () {
                            return that._ensureCount();
                        });
                    } else {
                        return this.selectAll();
                    }
                },

                add: function (items) {
                    /// <signature helpKeyword="WinJS.UI._Selection.prototype.add">
                    /// <summary locid="WinJS.UI._Selection.prototype.add">
                    /// Adds one or more items to the selection.
                    /// </summary>
                    /// <param name="items" locid="WinJS.UI._Selection.prototype.add_items">
                    /// The indexes or keys of the items to add.
                    /// You can provide different types of objects for the items parameter:
                    /// you can specify an index, a key, or a range of indexes.
                    /// It can also be an array that contains one or more of these objects.
                    /// </param>
                    /// <returns type="Promise" locid="WinJS.UI._Selection.prototype.add_returnValue">
                    /// A Promise that is fulfilled when the operation completes.
                    /// </returns>
                    /// </signature>

                    if (!isEverythingRange(items)) {
                        var that = this;
                        return this._execute("_add", items).then(function () {
                            return that._ensureKeys();
                        }).then(function () {
                            return that._ensureCount();
                        });
                    } else {
                        return this.selectAll();
                    }
                },

                remove: function (items) {
                    /// <signature helpKeyword="WinJS.UI._Selection.prototype.remove">
                    /// <summary locid="WinJS.UI._Selection.prototype.remove">
                    /// Removes the specified items from the selection.
                    /// </summary>
                    /// <param name="items" locid="WinJS.UI._Selection.prototype.remove_items">
                    /// The indexes or keys of the items to remove. You can provide different types of objects for the items parameter:
                    /// you can specify an index, a key, or a range of indexes.
                    /// It can also be an array that contains one or more of these objects.
                    /// </param>
                    /// <returns type="Promise" locid="WinJS.UI._Selection.prototype.remove_returnValue">
                    /// A Promise that is fulfilled when the operation completes.
                    /// </returns>
                    /// </signature>

                    var that = this;
                    return this._execute("_remove", items).then(function () {
                        return that._ensureKeys();
                    });
                },

                selectAll: function () {
                    /// <signature helpKeyword="WinJS.UI._Selection.prototype.selectAll">
                    /// <summary locid="WinJS.UI._Selection.prototype.selectAll">
                    /// Adds all the items in the ListView to the selection.
                    /// </summary>
                    /// <returns type="Promise" locid="WinJS.UI._Selection.prototype.selectAll_returnValue">
                    /// A Promise that is fulfilled when the operation completes.
                    /// </returns>
                    /// </signature>

                    var that = this;
                    return that._ensureCount().then(function () {
                        if (that._itemsCount) {
                            var range = {
                                firstIndex: 0,
                                lastIndex: that._itemsCount - 1,
                            };
                            that._retainRange(range);
                            that._releaseRanges(that._ranges);
                            that._ranges = [range];
                            return that._ensureKeys();
                        }
                    });
                },

                _execute: function (operation, items) {
                    var that = this,
                        keysSupported = !!that._getListBinding().fromKey,
                        array = Array.isArray(items) ? items : [items],
                        promises = [Promise.wrap()];

                    function toRange(type, first, last) {
                        var retVal = {};
                        retVal["first" + type] = first;
                        retVal["last" + type] = last;
                        return retVal;
                    }

                    function handleKeys(range) {
                        var binding = that._getListBinding();

                        var promise = Promise.join([binding.fromKey(range.firstKey), binding.fromKey(range.lastKey)]).then(function (items) {
                            if (items[0] && items[1]) {
                                range.firstIndex = items[0].index;
                                range.lastIndex = items[1].index;
                                that[operation](range);
                            }
                            return range;
                        });
                        promises.push(promise);
                    }

                    for (var i = 0, len = array.length; i < len; i++) {
                        var item = array[i];
                        if (typeof item === "number") {
                            this[operation](toRange("Index", item, item));
                        } else if (item) {
                            if (keysSupported && item.key !== undefined) {
                                handleKeys(toRange("Key", item.key, item.key));
                            } else if (keysSupported && item.firstKey !== undefined && item.lastKey !== undefined) {
                                handleKeys(toRange("Key", item.firstKey, item.lastKey));
                            } else if (item.index !== undefined && typeof item.index === "number") {
                                this[operation](toRange("Index", item.index, item.index));
                            } else if (item.firstIndex !== undefined && item.lastIndex !== undefined &&
                                    typeof item.firstIndex === "number" && typeof item.lastIndex === "number") {
                                this[operation](toRange("Index", item.firstIndex, item.lastIndex));
                            }
                        }
                    }

                    return Promise.join(promises);
                },

                _set: function (range) {
                    this._retainRange(range);
                    this._ranges.push(range);
                },

                _add: function (newRange) {
                    var that = this,
                        prev = null,
                        range,
                        inserted;

                    var merge = function (left, right) {
                        if (right.lastIndex > left.lastIndex) {
                            left.lastIndex = right.lastIndex;
                            left.lastKey = right.lastKey;
                            if (left.lastPromise) {
                                left.lastPromise.release();
                            }
                            left.lastPromise = that._getListBinding().fromIndex(left.lastIndex).retain();
                        }
                    };
                    var mergeWithPrev;
                    for (var i = 0, len = this._ranges.length; i < len; i++) {
                        range = this._ranges[i];
                        if (newRange.firstIndex < range.firstIndex) {
                            mergeWithPrev = prev && newRange.firstIndex < (prev.lastIndex + 1);
                            if (mergeWithPrev) {
                                inserted = i - 1;
                                merge(prev, newRange);
                            } else {
                                this._insertRange(i, newRange);
                                inserted = i;
                            }
                            break;
                        } else if (newRange.firstIndex === range.firstIndex) {
                            merge(range, newRange);
                            inserted = i;
                            break;
                        }
                        prev = range;
                    }

                    if (inserted === undefined) {
                        var last = this._ranges.length ? this._ranges[this._ranges.length - 1] : null,
                            mergeWithLast = last && newRange.firstIndex < (last.lastIndex + 1);
                        if (mergeWithLast) {
                            merge(last, newRange);
                        } else {
                            this._retainRange(newRange);
                            this._ranges.push(newRange);
                        }
                    } else {
                        prev = null;
                        for (i = inserted + 1, len = this._ranges.length; i < len; i++) {
                            range = this._ranges[i];
                            if (newRange.lastIndex < range.firstIndex) {
                                mergeWithPrev = prev && prev.lastIndex > newRange.lastIndex;
                                if (mergeWithPrev) {
                                    merge(this._ranges[inserted], prev);
                                }
                                this._removeRanges(inserted + 1, i - inserted - 1);
                                break;
                            } else if (newRange.lastIndex === range.firstIndex) {
                                merge(this._ranges[inserted], range);
                                this._removeRanges(inserted + 1, i - inserted);
                                break;
                            }
                            prev = range;
                        }
                        if (i >= len) {
                            merge(this._ranges[inserted], this._ranges[len - 1]);
                            this._removeRanges(inserted + 1, len - inserted - 1);
                        }
                    }
                },

                _remove: function (toRemove) {
                    var that = this;

                    function retainPromise(index) {
                        return that._getListBinding().fromIndex(index).retain();
                    }

                    // This method is called when a range needs to be unselected.  It is inspecting every range in the current selection comparing
                    // it to the range which is being unselected and it is building an array of new selected ranges
                    var ranges = [];
                    for (var i = 0, len = this._ranges.length; i < len; i++) {
                        var range = this._ranges[i];
                        if (range.lastIndex < toRemove.firstIndex || range.firstIndex > toRemove.lastIndex) {
                            // No overlap with the unselected range
                            ranges.push(range);
                        } else if (range.firstIndex < toRemove.firstIndex && range.lastIndex >= toRemove.firstIndex && range.lastIndex <= toRemove.lastIndex) {
                            // The end of this range is being unselected
                            ranges.push({
                                firstIndex: range.firstIndex,
                                firstKey: range.firstKey,
                                firstPromise: range.firstPromise,
                                lastIndex: toRemove.firstIndex - 1,
                                lastPromise: retainPromise(toRemove.firstIndex - 1)
                            });
                            range.lastPromise.release();
                        } else if (range.lastIndex > toRemove.lastIndex && range.firstIndex >= toRemove.firstIndex && range.firstIndex <= toRemove.lastIndex) {
                            // The beginning of this range is being unselected
                            ranges.push({
                                firstIndex: toRemove.lastIndex + 1,
                                firstPromise: retainPromise(toRemove.lastIndex + 1),
                                lastIndex: range.lastIndex,
                                lastKey: range.lastKey,
                                lastPromise: range.lastPromise
                            });
                            range.firstPromise.release();
                        } else if (range.firstIndex < toRemove.firstIndex && range.lastIndex > toRemove.lastIndex) {
                            // The middle part of this range is being unselected
                            ranges.push({
                                firstIndex: range.firstIndex,
                                firstKey: range.firstKey,
                                firstPromise: range.firstPromise,
                                lastIndex: toRemove.firstIndex - 1,
                                lastPromise: retainPromise(toRemove.firstIndex - 1),
                            });
                            ranges.push({
                                firstIndex: toRemove.lastIndex + 1,
                                firstPromise: retainPromise(toRemove.lastIndex + 1),
                                lastIndex: range.lastIndex,
                                lastKey: range.lastKey,
                                lastPromise: range.lastPromise
                            });
                        } else {
                            // The whole range is being unselected
                            range.firstPromise.release();
                            range.lastPromise.release();
                        }
                    }
                    this._ranges = ranges;
                },

                _ensureKeys: function () {
                    var promises = [Promise.wrap()];
                    var that = this;

                    var ensureKey = function (which, range) {
                        var keyProperty = which + "Key";

                        if (!range[keyProperty]) {
                            var promise = range[which + "Promise"];
                            promise.then(function (item) {
                                if (item) {
                                    range[keyProperty] = item.key;
                                }
                            });
                            return promise;
                        } else {
                            return Promise.wrap();
                        }
                    };

                    for (var i = 0, len = this._ranges.length; i < len; i++) {
                        var range = this._ranges[i];
                        promises.push(ensureKey("first", range));
                        promises.push(ensureKey("last", range));
                    }

                    Promise.join(promises).then(function () {
                        that._ranges = that._ranges.filter(function (range) {
                            return range.firstKey && range.lastKey;
                        });
                    });
                    return Promise.join(promises);
                },

                _mergeRanges: function (target, source) {
                    target.lastIndex = source.lastIndex;
                    target.lastKey = source.lastKey;
                },

                _isIncluded: function (index) {
                    if (this.isEverything()) {
                        return true;
                    } else {
                        for (var i = 0, len = this._ranges.length; i < len; i++) {
                            var range = this._ranges[i];
                            if (range.firstIndex <= index && index <= range.lastIndex) {
                                return true;
                            }
                        }
                        return false;
                    }
                },

                _ensureCount: function () {
                    var that = this;
                    return this._listView._itemsCount().then(function (count) {
                        that._itemsCount = count;
                    });
                },

                _insertRange: function (index, newRange) {
                    this._retainRange(newRange);
                    this._ranges.splice(index, 0, newRange);
                },

                _removeRanges: function (index, howMany) {
                    for (var i = 0; i < howMany; i++) {
                        this._releaseRange(this._ranges[index + i]);
                    }
                    this._ranges.splice(index, howMany);
                },

                _retainRange: function (range) {
                    if (!range.firstPromise) {
                        range.firstPromise = this._getListBinding().fromIndex(range.firstIndex).retain();
                    }
                    if (!range.lastPromise) {
                        range.lastPromise = this._getListBinding().fromIndex(range.lastIndex).retain();
                    }
                },

                _retainRanges: function () {
                    for (var i = 0, len = this._ranges.length; i < len; i++) {
                        this._retainRange(this._ranges[i]);
                    }
                },

                _releaseRange: function (range) {
                    range.firstPromise.release();
                    range.lastPromise.release();
                },

                _releaseRanges: function (ranges) {
                    for (var i = 0, len = ranges.length; i < len; ++i) {
                        this._releaseRange(ranges[i]);
                    }
                },

                _getListBinding: function () {
                    return this._listView._itemsManager._listBinding;
                }
            }, {
                supportedForProcessing: false,
            });
        }),

        // This component is responsible for holding selection state
        _SelectionManager: _Base.Namespace._lazy(function () {
            var _SelectionManager = function (listView) {
                this._listView = listView;
                this._selected = new exports._Selection(this._listView);
                // Don't rename this member. Some apps reference it.
                this._pivot = _Constants._INVALID_INDEX;
                this._focused = { type: _UI.ObjectType.item, index: 0 };
                this._pendingChange = Promise.wrap();
            };
            _SelectionManager.prototype = {
                count: function () {
                    /// <signature helpKeyword="WinJS.UI._SelectionManager.prototype.count">
                    /// <summary locid="WinJS.UI._SelectionManager.prototype.count">
                    /// Returns the number of items in the selection.
                    /// </summary>
                    /// <returns type="Number" locid="WinJS.UI._SelectionManager.prototype.count_returnValue">
                    /// The number of items in the selection.
                    /// </returns>
                    /// </signature>
                    return this._selected.count();
                },

                getIndices: function () {
                    /// <signature helpKeyword="WinJS.UI._SelectionManager.prototype.getIndices">
                    /// <summary locid="WinJS.UI._SelectionManager.prototype.getIndices">
                    /// Returns a list of the indexes for the items in the selection.
                    /// </summary>
                    /// <returns type="Array" locid="WinJS.UI._SelectionManager.prototype.getIndices_returnValue">
                    /// The list of indexes for the items in the selection as an array of Number objects.
                    /// </returns>
                    /// </signature>
                    return this._selected.getIndices();
                },

                getItems: function () {
                    /// <signature helpKeyword="WinJS.UI._SelectionManager.prototype.getItems">
                    /// <summary locid="WinJS.UI._SelectionManager.prototype.getItems">
                    /// Returns an array that contains the items in the selection.
                    /// </summary>
                    /// <returns type="Promise" locid="WinJS.UI._SelectionManager.prototype.getItems_returnValue">
                    /// A Promise that contains an array of the requested IItem objects.
                    /// </returns>
                    /// </signature>

                    return this._selected.getItems();
                },

                getRanges: function () {
                    /// <signature helpKeyword="WinJS.UI._SelectionManager.prototype.getRanges">
                    /// <summary locid="WinJS.UI._SelectionManager.prototype.getRanges">
                    /// Gets an array of the index ranges for the selected items.
                    /// </summary>
                    /// <returns type="Array" locid="WinJS.UI._SelectionManager.prototype.getRanges_returnValue">
                    /// An array that contains an ISelectionRange object for each index range in the selection.
                    /// </returns>
                    /// </signature>
                    return this._selected.getRanges();
                },

                isEverything: function () {
                    /// <signature helpKeyword="WinJS.UI._SelectionManager.prototype.isEverything">
                    /// <summary locid="WinJS.UI._SelectionManager.prototype.isEverything">
                    /// Returns a value that indicates whether the selection contains every item in the data source.
                    /// </summary>
                    /// <returns type="Boolean" locid="WinJS.UI._SelectionManager.prototype.isEverything_returnValue">
                    /// true if the selection contains every item in the data source; otherwise, false.
                    /// </returns>
                    /// </signature>
                    return this._selected.isEverything();
                },

                set: function (items) {
                    /// <signature helpKeyword="WinJS.UI._SelectionManager.prototype.set">
                    /// <summary locid="WinJS.UI._SelectionManager.prototype.set">
                    /// Clears the current selection and replaces it with the specified items.
                    /// </summary>
                    /// <param name="items" locid="WinJS.UI._SelectionManager.prototype.set_items">
                    /// The indexes or keys of the items that make up the selection.
                    /// You can provide different types of objects for the items parameter:
                    /// you can specify an index, a key, or a range of indexes.
                    /// It can also be an array that contains one or more of these objects.
                    /// </param>
                    /// <returns type="Promise" locid="WinJS.UI._SelectionManager.prototype.set_returnValue">
                    /// A Promise that is fulfilled when the operation completes.
                    /// </returns>
                    /// </signature>
                    var that = this,
                        signal = new _Signal();
                    return this._synchronize(signal).then(function () {
                        var newSelection = new exports._Selection(that._listView);
                        return newSelection.set(items).then(
                            function () {
                                that._set(newSelection);
                                signal.complete();
                            },
                            function (error) {
                                newSelection.clear();
                                signal.complete();
                                return Promise.wrapError(error);
                            }
                        );
                    });
                },

                clear: function () {
                    /// <signature helpKeyword="WinJS.UI._SelectionManager.prototype.clear">
                    /// <summary locid="WinJS.UI._SelectionManager.prototype.clear">
                    /// Clears the selection.
                    /// </summary>
                    /// <returns type="Promise" locid="WinJS.UI._SelectionManager.prototype.clear_returnValue">
                    /// A Promise that is fulfilled when the clear operation completes.
                    /// </returns>
                    /// </signature>

                    var that = this,
                        signal = new _Signal();
                    return this._synchronize(signal).then(function () {
                        var newSelection = new exports._Selection(that._listView);
                        return newSelection.clear().then(
                            function () {
                                that._set(newSelection);
                                signal.complete();
                            },
                            function (error) {
                                newSelection.clear();
                                signal.complete();
                                return Promise.wrapError(error);
                            }
                        );
                    });
                },

                add: function (items) {
                    /// <signature helpKeyword="WinJS.UI._SelectionManager.prototype.add">
                    /// <summary locid="WinJS.UI._SelectionManager.prototype.add">
                    /// Adds one or more items to the selection.
                    /// </summary>
                    /// <param name="items" locid="WinJS.UI._SelectionManager.prototype.add_items">
                    /// The indexes or keys of the items to add.
                    /// You can provide different types of objects for the items parameter:
                    /// you can specify an index, a key, or a range of indexes.
                    /// It can also be an array that contains one or more of these objects.
                    /// </param>
                    /// <returns type="Promise" locid="WinJS.UI._SelectionManager.prototype.add_returnValue">
                    /// A Promise that is fulfilled when the operation completes.
                    /// </returns>
                    /// </signature>
                    var that = this,
                        signal = new _Signal();
                    return this._synchronize(signal).then(function () {
                        var newSelection = that._cloneSelection();
                        return newSelection.add(items).then(
                            function () {
                                that._set(newSelection);
                                signal.complete();
                            },
                            function (error) {
                                newSelection.clear();
                                signal.complete();
                                return Promise.wrapError(error);
                            }
                        );
                    });
                },

                remove: function (items) {
                    /// <signature helpKeyword="WinJS.UI._SelectionManager.prototype.remove">
                    /// <summary locid="WinJS.UI._SelectionManager.prototype.remove">
                    /// Removes the specified items from the selection.
                    /// </summary>
                    /// <param name="items" locid="WinJS.UI._SelectionManager.prototype.remove_items">
                    /// The indexes or keys of the items to remove. You can provide different types of objects for the items parameter:
                    /// you can specify an index, a key, or a range of indexes.
                    /// It can also be an array that contains one or more of these objects.
                    /// </param>
                    /// <returns type="Promise" locid="WinJS.UI._SelectionManager.prototype.remove_returnValue">
                    /// A Promise that is fulfilled when the operation completes.
                    /// </returns>
                    /// </signature>
                    var that = this,
                        signal = new _Signal();
                    return this._synchronize(signal).then(function () {
                        var newSelection = that._cloneSelection();
                        return newSelection.remove(items).then(
                            function () {
                                that._set(newSelection);
                                signal.complete();
                            },
                            function (error) {
                                newSelection.clear();
                                signal.complete();
                                return Promise.wrapError(error);
                            }
                        );
                    });
                },

                selectAll: function () {
                    /// <signature helpKeyword="WinJS.UI._SelectionManager.prototype.selectAll">
                    /// <summary locid="WinJS.UI._SelectionManager.prototype.selectAll">
                    /// Adds all the items in the ListView to the selection.
                    /// </summary>
                    /// <returns type="Promise" locid="WinJS.UI._SelectionManager.prototype.selectAll_returnValue">
                    /// A Promise that is fulfilled when the operation completes.
                    /// </returns>
                    /// </signature>
                    var that = this,
                        signal = new _Signal();
                    return this._synchronize(signal).then(function () {
                        var newSelection = new exports._Selection(that._listView);
                        return newSelection.selectAll().then(
                            function () {
                                that._set(newSelection);
                                signal.complete();
                            },
                            function (error) {
                                newSelection.clear();
                                signal.complete();
                                return Promise.wrapError(error);
                            }
                        );
                    });
                },

                _synchronize: function (signal) {
                    var that = this;
                    return this._listView._versionManager.unlocked.then(function () {
                        var currentPendingChange = that._pendingChange;
                        that._pendingChange = Promise.join([currentPendingChange, signal.promise]).then(function () { });
                        return currentPendingChange;
                    });
                },

                _reset: function () {
                    this._pivot = _Constants._INVALID_INDEX;
                    this._setFocused({ type: _UI.ObjectType.item, index: 0 }, this._keyboardFocused());
                    this._pendingChange.cancel();
                    this._pendingChange = Promise.wrap();
                    this._selected.clear();
                    this._selected = new exports._Selection(this._listView);
                },

                _dispose: function () {
                    this._selected.clear();
                    this._selected = null;
                    this._listView = null;
                },

                _set: function (newSelection) {
                    var that = this;
                    return this._fireSelectionChanging(newSelection).then(function (approved) {
                        if (approved) {
                            that._selected.clear();
                            that._selected = newSelection;
                            that._listView._updateSelection();
                            that._fireSelectionChanged();
                        } else {
                            newSelection.clear();
                        }
                        return approved;
                    });
                },

                _fireSelectionChanging: function (newSelection) {
                    var eventObject = _Global.document.createEvent("CustomEvent"),
                        newSelectionUpdated = Promise.wrap();

                    eventObject.initCustomEvent("selectionchanging", true, true, {
                        newSelection: newSelection,
                        preventTapBehavior: function () {
                        },
                        setPromise: function (promise) {
                            /// <signature helpKeyword="WinJS.UI.SelectionManager.selectionchanging.setPromise">
                            /// <summary locid="WinJS.UI.SelectionManager.selectionchanging.setPromise">
                            /// Used to inform the ListView that asynchronous work is being performed, and that this
                            /// event handler should not be considered complete until the promise completes.
                            /// </summary>
                            /// <param name="promise" type="WinJS.Promise" locid="WinJS.UI.SelectionManager.selectionchanging.setPromise_p:promise">
                            /// The promise to wait for.
                            /// </param>
                            /// </signature>

                            newSelectionUpdated = promise;
                        }
                    });

                    var approved = this._listView._element.dispatchEvent(eventObject);
                    return newSelectionUpdated.then(function () {
                        return approved;
                    });
                },

                _fireSelectionChanged: function () {
                    var eventObject = _Global.document.createEvent("CustomEvent");
                    eventObject.initCustomEvent("selectionchanged", true, false, null);
                    this._listView._element.dispatchEvent(eventObject);
                },

                _getFocused: function () {
                    return { type: this._focused.type, index: this._focused.index };
                },

                _setFocused: function (entity, keyboardFocused) {
                    this._focused = { type: entity.type, index: entity.index };
                    this._focusedByKeyboard = keyboardFocused;
                },

                _keyboardFocused: function () {
                    return this._focusedByKeyboard;
                },

                _updateCount: function (count) {
                    this._selected._itemsCount = count;
                },

                _isIncluded: function (index) {
                    return this._selected._isIncluded(index);
                },

                _cloneSelection: function () {
                    var newSelection = new exports._Selection(this._listView);
                    newSelection._ranges = this._selected.getRanges();
                    newSelection._itemsCount = this._selected._itemsCount;
                    newSelection._retainRanges();
                    return newSelection;
                }
            };
            _SelectionManager.supportedForProcessing = false;
            return _SelectionManager;
        })
    });

});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/ListView/_BrowseMode',[
    'exports',
    '../../Core/_Global',
    '../../Core/_Base',
    '../../Core/_BaseUtils',
    '../../Animations',
    '../../Promise',
    '../../Utilities/_ElementUtilities',
    '../../Utilities/_UI',
    '../ItemContainer/_Constants',
    '../ItemContainer/_ItemEventsHandler',
    './_SelectionManager'
    ], function browseModeInit(exports, _Global, _Base, _BaseUtils, Animations, Promise, _ElementUtilities, _UI, _Constants, _ItemEventsHandler, _SelectionManager) {
    "use strict";

    var transformName = _BaseUtils._browserStyleEquivalents["transform"].scriptName;
    // This component is responsible for handling input in Browse Mode.
    // When the user clicks on an item in this mode itemInvoked event is fired.
    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {

        _SelectionMode: _Base.Namespace._lazy(function () {

            function clampToRange(first, last, x) {
                return Math.max(first, Math.min(last, x));
            }

            function dispatchKeyboardNavigating(element, oldEntity, newEntity) {
                var navigationEvent = _Global.document.createEvent("CustomEvent");
                navigationEvent.initCustomEvent("keyboardnavigating", true, true, {
                    oldFocus: oldEntity.index,
                    oldFocusType: oldEntity.type,
                    newFocus: newEntity.index,
                    newFocusType: newEntity.type
                });
                return element.dispatchEvent(navigationEvent);
            }

            var _SelectionMode = _Base.Class.define(function _SelectionMode_ctor(modeSite) {
                this.inboundFocusHandled = false;
                this._pressedContainer = null;
                this._pressedItemBox = null;
                this._pressedHeader = null;
                this._pressedEntity = { type: _UI.ObjectType.item, index: _Constants._INVALID_INDEX };
                this._pressedPosition = null;

                this.initialize(modeSite);
            },{
                _dispose: function () {
                    if (this._itemEventsHandler) {
                        this._itemEventsHandler.dispose();
                    }
                    if (this._setNewFocusItemOffsetPromise) {
                        this._setNewFocusItemOffsetPromise.cancel();
                    }
                },

                initialize: function (modeSite) {
                    this.site = modeSite;

                    this._keyboardNavigationHandlers = {};
                    this._keyboardAcceleratorHandlers = {};

                    var site = this.site,
                        that = this;
                    this._itemEventsHandler = new _ItemEventsHandler._ItemEventsHandler(Object.create({
                        containerFromElement: function (element) {
                            return site._view.items.containerFrom(element);
                        },
                        indexForItemElement: function (element) {
                            return site._view.items.index(element);
                        },
                        indexForHeaderElement: function (element) {
                            return site._groups.index(element);
                        },
                        itemBoxAtIndex: function (index) {
                            return site._view.items.itemBoxAt(index);
                        },
                        itemAtIndex: function (index) {
                            return site._view.items.itemAt(index);
                        },
                        headerAtIndex: function (index) {
                            return site._groups.group(index).header;
                        },
                        headerFromElement: function (element) {
                            return site._groups.headerFrom(element);
                        },
                        containerAtIndex: function (index) {
                            return site._view.items.containerAt(index);
                        },
                        isZombie: function () {
                            return site._isZombie();
                        },
                        getItemPosition: function (entity) {
                            return site._getItemPosition(entity);
                        },
                        rtl: function () {
                            return site._rtl();
                        },
                        fireInvokeEvent: function (entity, itemElement) {
                            return that._fireInvokeEvent(entity, itemElement);
                        },
                        verifySelectionAllowed: function (index) {
                            return that._verifySelectionAllowed(index);
                        },
                        changeFocus: function (newFocus, skipSelection, ctrlKeyDown, skipEnsureVisible, keyboardFocused) {
                            return site._changeFocus(newFocus, skipSelection, ctrlKeyDown, skipEnsureVisible, keyboardFocused);
                        },
                        selectRange: function (firstIndex, lastIndex, additive) {
                            return that._selectRange(firstIndex, lastIndex, additive);
                        }
                    }, {
                        pressedEntity: {
                            enumerable: true,
                            get: function () {
                                return that._pressedEntity;
                            },
                            set: function (value) {
                                that._pressedEntity = value;
                            }
                        },
                        pressedContainerScaleTransform: {
                            enumerable: true,
                            get: function () {
                                return that._pressedContainerScaleTransform;
                            },
                            set: function (value) {
                                that._pressedContainerScaleTransform = value;
                            }
                        },
                        pressedContainer: {
                            enumerable: true,
                            get: function () {
                                return that._pressedContainer;
                            },
                            set: function (value) {
                                that._pressedContainer = value;
                            }
                        },

                        pressedItemBox: {
                            enumerable: true,
                            get: function () {
                                return that._pressedItemBox;
                            },
                            set: function (value) {
                                that._pressedItemBox = value;
                            }
                        },

                        pressedHeader: {
                            enumerable: true,
                            get: function () {
                                return that._pressedHeader;
                            },
                            set: function (value) {
                                return that._pressedHeader = value;
                            }
                        },

                        pressedPosition: {
                            enumerable: true,
                            get: function () {
                                return that._pressedPosition;
                            },
                            set: function (value) {
                                that._pressedPosition = value;
                            }
                        },

                        pressedElement: {
                            enumerable: true,
                            set: function (value) {
                                that._pressedElement = value;
                            }
                        },

                        swipeBehavior: {
                            enumerable: true,
                            get: function () {
                                return site._swipeBehavior;
                            }
                        },
                        eventHandlerRoot: {
                            enumerable: true,
                            get: function () {
                                return site._viewport;
                            }
                        },
                        selectionMode: {
                            enumerable: true,
                            get: function () {
                                return site._selectionMode;
                            }
                        },
                        accessibleItemClass: {
                            enumerable: true,
                            get: function () {
                                // CSS class of the element with the aria role
                                return _Constants._itemClass;
                            }
                        },
                        canvasProxy: {
                            enumerable: true,
                            get: function () {
                                return site._canvasProxy;
                            }
                        },
                        tapBehavior: {
                            enumerable: true,
                            get: function () {
                                return site._tap;
                            }
                        },
                        headerTapBehavior: {
                            enumerable: true,
                            get: function () {
                                return site._groupHeaderTap;
                            }
                        },
                        draggable: {
                            enumerable: true,
                            get: function () {
                                return site.itemsDraggable || site.itemsReorderable;
                            }
                        },
                        selection: {
                            enumerable: true,
                            get: function () {
                                return site._selection;
                            }
                        },
                        horizontal: {
                            enumerable: true,
                            get: function () {
                                return site._horizontal();
                            }
                        },
                        customFootprintParent: {
                            enumerable: true,
                            get: function () {
                                return null;
                            }
                        }
                    }));

                    function createArrowHandler(direction, clampToBounds) {
                        var handler = function (oldFocus) {
                            return modeSite._view.getAdjacent(oldFocus, direction);
                        };
                        handler.clampToBounds = clampToBounds;
                        return handler;
                    }

                    var Key = _ElementUtilities.Key;
                    this._keyboardNavigationHandlers[Key.upArrow] = createArrowHandler(Key.upArrow);
                    this._keyboardNavigationHandlers[Key.downArrow] = createArrowHandler(Key.downArrow);
                    this._keyboardNavigationHandlers[Key.leftArrow] = createArrowHandler(Key.leftArrow);
                    this._keyboardNavigationHandlers[Key.rightArrow] = createArrowHandler(Key.rightArrow);
                    this._keyboardNavigationHandlers[Key.pageUp] = createArrowHandler(Key.pageUp, true);
                    this._keyboardNavigationHandlers[Key.pageDown] = createArrowHandler(Key.pageDown, true);
                    this._keyboardNavigationHandlers[Key.home] = function (oldFocus) {
                        return Promise.wrap({ type: oldFocus.type, index: 0 });
                    };
                    this._keyboardNavigationHandlers[Key.end] = function (oldFocus) {
                        if (oldFocus.type === _UI.ObjectType.groupHeader) {
                            return Promise.wrap({ type: oldFocus.type, index: site._groups.length() - 1 });
                        } else {
                            // Get the index of the last container
                            return that.site._view.finalItem().then(function (index) {
                                return { type: oldFocus.type, index: index };
                            }, function (error) {
                                return Promise.wrapError(error);
                            });
                        }
                    };

                    this._keyboardAcceleratorHandlers[Key.a] = function () {
                        if (that.site._multiSelection()) {
                            that._selectAll();
                        }
                    };
                },

                staticMode: function SelectionMode_staticMode() {
                    return this.site._tap === _UI.TapBehavior.none && this.site._selectionMode === _UI.SelectionMode.none;
                },

                itemUnrealized: function SelectionMode_itemUnrealized(index, itemBox) {
                    if (this._pressedEntity.type === _UI.ObjectType.groupHeader) {
                        return;
                    }

                    if (this._pressedEntity.index === index) {
                        this._resetPointerDownState();
                    }

                    if (this._itemBeingDragged(index)) {
                        for (var i = this._draggedItemBoxes.length - 1; i >= 0; i--) {
                            if (this._draggedItemBoxes[i] === itemBox) {
                                _ElementUtilities.removeClass(itemBox, _Constants._dragSourceClass);
                                this._draggedItemBoxes.splice(i, 1);
                            }
                        }
                    }
                },

                _fireInvokeEvent: function SelectionMode_fireInvokeEvent(entity, itemElement) {
                    if (!itemElement) {
                        return;
                    }

                    var that = this;
                    function fireInvokeEventImpl(dataSource, isHeader) {
                        var listBinding = dataSource.createListBinding(),
                             promise = listBinding.fromIndex(entity.index),
                             eventName = isHeader ? "groupheaderinvoked" : "iteminvoked";

                        promise.done(function () {
                            listBinding.release();
                        });

                        var eventObject = _Global.document.createEvent("CustomEvent");
                        eventObject.initCustomEvent(eventName, true, true, isHeader ? {
                            groupHeaderPromise: promise,
                            groupHeaderIndex: entity.index
                        } : {
                            itemPromise: promise,
                            itemIndex: entity.index
                        });

                        // If preventDefault was not called, call the default action on the site
                        if (itemElement.dispatchEvent(eventObject)) {
                            that.site._defaultInvoke(entity);
                        }
                    }

                    if (entity.type === _UI.ObjectType.groupHeader) {
                        if (this.site._groupHeaderTap === _UI.GroupHeaderTapBehavior.invoke &&
                            entity.index !== _Constants._INVALID_INDEX) {
                            fireInvokeEventImpl(this.site.groupDataSource, true);
                        }
                    } else {
                        if (this.site._tap !== _UI.TapBehavior.none && entity.index !== _Constants._INVALID_INDEX) {
                            fireInvokeEventImpl(this.site.itemDataSource, false);
                        }
                    }
                },

                _verifySelectionAllowed: function SelectionMode_verifySelectionAllowed(entity) {
                    if (entity.type === _UI.ObjectType.groupHeader) {
                        return {
                            canSelect: false,
                            canTapSelect: false
                        };
                    }

                    var itemIndex = entity.index;
                    var site = this.site;
                    var item = this.site._view.items.itemAt(itemIndex);
                    if (site._selectionAllowed() && (site._selectOnTap() || site._swipeBehavior === _UI.SwipeBehavior.select) && !(item && _ElementUtilities.hasClass(item, _Constants._nonSelectableClass))) {
                        var selected = site._selection._isIncluded(itemIndex),
                            single = !site._multiSelection(),
                            newSelection = site._selection._cloneSelection();

                        if (selected) {
                            if (single) {
                                newSelection.clear();
                            } else {
                                newSelection.remove(itemIndex);
                            }
                        } else {
                            if (single) {
                                newSelection.set(itemIndex);
                            } else {
                                newSelection.add(itemIndex);
                            }
                        }

                        var eventObject = _Global.document.createEvent("CustomEvent"),
                            newSelectionUpdated = Promise.wrap(),
                            completed = false,
                            preventTap = false,
                            included;

                        eventObject.initCustomEvent("selectionchanging", true, true, {
                            newSelection: newSelection,
                            preventTapBehavior: function () {
                                preventTap = true;
                            },
                            setPromise: function (promise) {
                                /// <signature helpKeyword="WinJS.UI.BrowseMode.selectionchanging.setPromise">
                                /// <summary locid="WinJS.UI.BrowseMode.selectionchanging.setPromise">
                                /// Used to inform the ListView that asynchronous work is being performed, and that this
                                /// event handler should not be considered complete until the promise completes.
                                /// </summary>
                                /// <param name="promise" type="WinJS.Promise" locid="WinJS.UI.BrowseMode.selectionchanging.setPromise_p:promise">
                                /// The promise to wait for.
                                /// </param>
                                /// </signature>

                                newSelectionUpdated = promise;
                            }
                        });

                        var defaultBehavior = site._element.dispatchEvent(eventObject);

                        newSelectionUpdated.then(function () {
                            completed = true;
                            included = newSelection._isIncluded(itemIndex);
                            newSelection.clear();
                        });

                        var canSelect = defaultBehavior && completed && (selected || included);

                        return {
                            canSelect: canSelect,
                            canTapSelect: canSelect && !preventTap
                        };
                    } else {
                        return {
                            canSelect: false,
                            canTapSelect: false
                        };
                    }
                },

                _containedInElementWithClass: function SelectionMode_containedInElementWithClass(element, className) {
                    if (element.parentNode) {
                        var matches = element.parentNode.querySelectorAll("." + className + ", ." + className + " *");
                        for (var i = 0, len = matches.length; i < len; i++) {
                            if (matches[i] === element) {
                                return true;
                            }
                        }
                    }
                    return false;
                },

                _isDraggable: function SelectionMode_isDraggable(element) {
                    return (!this._containedInElementWithClass(element, _Constants._nonDraggableClass));
                },

                _isInteractive: function SelectionMode_isInteractive(element) {
                    return this._containedInElementWithClass(element, "win-interactive");
                },

                _resetPointerDownState: function SelectionMode_resetPointerDownState() {
                    this._itemEventsHandler.resetPointerDownState();
                },

                onMSManipulationStateChanged: function (eventObject) {
                    this._itemEventsHandler.onMSManipulationStateChanged(eventObject);
                },

                onPointerDown: function SelectionMode_onPointerDown(eventObject) {
                    this._itemEventsHandler.onPointerDown(eventObject);
                },

                onclick: function SelectionMode_onclick(eventObject) {
                    this._itemEventsHandler.onClick(eventObject);
                },

                onPointerUp: function SelectionMode_onPointerUp(eventObject) {
                    this._itemEventsHandler.onPointerUp(eventObject);
                },

                onPointerCancel: function SelectionMode_onPointerCancel(eventObject) {
                    this._itemEventsHandler.onPointerCancel(eventObject);
                },

                onLostPointerCapture: function SelectionMode_onLostPointerCapture(eventObject) {
                    this._itemEventsHandler.onLostPointerCapture(eventObject);
                },

                onContextMenu: function SelectionMode_onContextMenu(eventObject) {
                    this._itemEventsHandler.onContextMenu(eventObject);
                },

                onMSHoldVisual: function SelectionMode_onMSHoldVisual(eventObject) {
                    this._itemEventsHandler.onMSHoldVisual(eventObject);
                },

                onDataChanged: function SelectionMode_onDataChanged(eventObject) {
                    this._itemEventsHandler.onDataChanged(eventObject);
                },

                _removeTransform: function SelectionMode_removeTransform(element, transform) {
                    if (transform && element.style[transformName].indexOf(transform) !== -1) {
                        element.style[transformName] = element.style[transformName].replace(transform, "");
                    }
                },

                _selectAll: function SelectionMode_selectAll() {
                    var unselectableRealizedItems = [];
                    this.site._view.items.each(function (index, item) {
                        if (item && _ElementUtilities.hasClass(item, _Constants._nonSelectableClass)) {
                            unselectableRealizedItems.push(index);
                        }
                    });

                    this.site._selection.selectAll();
                    if (unselectableRealizedItems.length > 0) {
                        this.site._selection.remove(unselectableRealizedItems);
                    }
                },

                _selectRange: function SelectionMode_selectRange(firstIndex, lastIndex, additive) {
                    var ranges = [];
                    var currentStartRange = -1;
                    for (var i = firstIndex; i <= lastIndex; i++) {
                        var item = this.site._view.items.itemAt(i);
                        if (item && _ElementUtilities.hasClass(item, _Constants._nonSelectableClass)) {
                            if (currentStartRange !== -1) {
                                ranges.push({
                                    firstIndex: currentStartRange,
                                    lastIndex: i - 1
                                });
                                currentStartRange = -1;
                            }
                        } else if (currentStartRange === -1) {
                            currentStartRange = i;
                        }
                    }
                    if (currentStartRange !== -1) {
                        ranges.push({
                            firstIndex: currentStartRange,
                            lastIndex: lastIndex
                        });
                    }
                    if (ranges.length > 0) {
                        this.site._selection[additive ? "add" : "set"](ranges);
                    }
                },

                onDragStart: function SelectionMode_onDragStart(eventObject) {
                    this._pressedEntity = { type: _UI.ObjectType.item, index: this.site._view.items.index(eventObject.target) };
                    this.site._selection._pivot = _Constants._INVALID_INDEX;
                    // Drag shouldn't be initiated when the user holds down the mouse on a win-interactive element and moves.
                    // The problem is that the dragstart event's srcElement+target will both be an itembox (which has draggable=true), so we can't check for win-interactive in the dragstart event handler.
                    // The itemEventsHandler sets our _pressedElement field on MSPointerDown, so we use that instead when checking for interactive.
                    if (this._pressedEntity.index !== _Constants._INVALID_INDEX &&
                            (this.site.itemsDraggable || this.site.itemsReorderable) &&
                            !this.site._view.animating &&
                            this._isDraggable(eventObject.target) &&
                            (!this._pressedElement || !this._isInteractive(this._pressedElement))) {
                        this._dragging = true;
                        this._dragDataTransfer = eventObject.dataTransfer;
                        this._pressedPosition = _ElementUtilities._getCursorPos(eventObject);
                        this._dragInfo = null;
                        this._lastEnteredElement = eventObject.target;

                        if (this.site._selection._isIncluded(this._pressedEntity.index)) {
                            this._dragInfo = this.site.selection;
                        } else {
                            this._draggingUnselectedItem = true;
                            this._dragInfo = new _SelectionManager._Selection(this.site, [{ firstIndex: this._pressedEntity.index, lastIndex: this._pressedEntity.index }]);
                        }

                        var dropTarget = this.site.itemsReorderable;
                        var event = _Global.document.createEvent("CustomEvent");
                        event.initCustomEvent("itemdragstart", true, false, {
                            dataTransfer: eventObject.dataTransfer,
                            dragInfo: this._dragInfo
                        });

                        // Firefox requires setData to be called on the dataTransfer object in order for DnD to continue.
                        // Firefox also has an issue rendering the item's itemBox+element, so we need to use setDragImage, using the item's container, to get it to render.
                        eventObject.dataTransfer.setData("text", "");
                        if (eventObject.dataTransfer.setDragImage) {
                            var pressedItemData = this.site._view.items.itemDataAt(this._pressedEntity.index);
                            if (pressedItemData && pressedItemData.container) {
                                var rect = pressedItemData.container.getBoundingClientRect();
                                eventObject.dataTransfer.setDragImage(pressedItemData.container, eventObject.clientX - rect.left, eventObject.clientY - rect.top);
                            }
                        }
                        this.site.element.dispatchEvent(event);
                        if (this.site.itemsDraggable && !this.site.itemsReorderable) {
                            if (!this._firedDragEnter) {
                                if (this._fireDragEnterEvent(eventObject.dataTransfer)) {
                                    dropTarget = true;
                                    this._dragUnderstood = true;
                                }
                            }
                        }

                        if (dropTarget) {
                            this._addedDragOverClass = true;
                            _ElementUtilities.addClass(this.site._element, _Constants._dragOverClass);
                        }

                        this._draggedItemBoxes = [];

                        var that = this;
                        // A dragged element can be removed from the DOM by a number of actions - datasource removes/changes, being scrolled outside of the realized range, etc.
                        // The dragend event is fired on the original source element of the drag. If that element isn't in the DOM, though, the dragend event will only be fired on the element
                        // itself and not bubble up through the ListView's tree to the _viewport element where all the other drag event handlers are.
                        // The dragend event handler has to be added to the event's srcElement so that we always receive the event, even when the source element is unrealized.
                        var sourceElement = eventObject.target;
                        sourceElement.addEventListener("dragend", function itemDragEnd(eventObject) {
                            sourceElement.removeEventListener("dragend", itemDragEnd);
                            that.onDragEnd(eventObject);
                        });
                        // We delay setting the opacity of the dragged items so that IE has time to create a thumbnail before me make them invisible
                        _BaseUtils._yieldForDomModification(function () {
                            if (that._dragging) {
                                var indicesSelected = that._dragInfo.getIndices();
                                for (var i = 0, len = indicesSelected.length; i < len; i++) {
                                    var itemData = that.site._view.items.itemDataAt(indicesSelected[i]);
                                    if (itemData && itemData.itemBox) {
                                        that._addDragSourceClass(itemData.itemBox);
                                    }
                                }
                            }
                        });
                    } else {
                        eventObject.preventDefault();
                    }
                },

                onDragEnter: function (eventObject) {
                    var eventHandled = this._dragUnderstood;
                    this._lastEnteredElement = eventObject.target;
                    if (this._exitEventTimer) {
                        _Global.clearTimeout(this._exitEventTimer);
                        this._exitEventTimer = 0;
                    }

                    if (!this._firedDragEnter) {
                        if (this._fireDragEnterEvent(eventObject.dataTransfer)) {
                            eventHandled = true;
                        }
                    }

                    if (eventHandled || (this._dragging && this.site.itemsReorderable)) {
                        eventObject.preventDefault();
                        this._dragUnderstood = true;
                        if (!this._addedDragOverClass) {
                            this._addedDragOverClass = true;
                            _ElementUtilities.addClass(this.site._element, _Constants._dragOverClass);
                        }
                    }
                    this._pointerLeftRegion = false;
                },

                onDragLeave: function (eventObject) {
                    if (eventObject.target === this._lastEnteredElement) {
                        this._pointerLeftRegion = true;
                        this._handleExitEvent();
                    }
                },

                fireDragUpdateEvent: function () {
                    var event = _Global.document.createEvent("CustomEvent");
                    event.initCustomEvent("itemdragchanged", true, false, {
                        dataTransfer: this._dragDataTransfer,
                        dragInfo: this._dragInfo
                    });
                    this.site.element.dispatchEvent(event);
                },

                _fireDragEnterEvent: function (dataTransfer) {
                    var event = _Global.document.createEvent("CustomEvent");
                    event.initCustomEvent("itemdragenter", true, true, {
                        dataTransfer: dataTransfer
                    });
                    // The end developer must tell a ListView when a drag can be understood by calling preventDefault() on the event we fire
                    var dropTarget = (!this.site.element.dispatchEvent(event));
                    this._firedDragEnter = true;
                    return dropTarget;
                },

                _fireDragBetweenEvent: function (index, insertAfterIndex, dataTransfer) {
                    var event = _Global.document.createEvent("CustomEvent");
                    event.initCustomEvent("itemdragbetween", true, true, {
                        index: index,
                        insertAfterIndex: insertAfterIndex,
                        dataTransfer: dataTransfer
                    });
                    return this.site.element.dispatchEvent(event);
                },

                _fireDropEvent: function (index, insertAfterIndex, dataTransfer) {
                    var event = _Global.document.createEvent("CustomEvent");
                    event.initCustomEvent("itemdragdrop", true, true, {
                        index: index,
                        insertAfterIndex: insertAfterIndex,
                        dataTransfer: dataTransfer
                    });
                    return this.site.element.dispatchEvent(event);
                },

                _handleExitEvent: function () {
                    if (this._exitEventTimer) {
                        _Global.clearTimeout(this._exitEventTimer);
                        this._exitEventTimer = 0;
                    }
                    var that = this;
                    this._exitEventTimer = _Global.setTimeout(function () {
                        if (that.site._disposed) { return; }

                        if (that._pointerLeftRegion) {
                            that.site._layout.dragLeave && that.site._layout.dragLeave();
                            that._pointerLeftRegion = false;
                            that._dragUnderstood = false;
                            that._lastEnteredElement = null;
                            that._lastInsertPoint = null;
                            that._dragBetweenDisabled = false;
                            if (that._firedDragEnter) {
                                var event = _Global.document.createEvent("CustomEvent");
                                event.initCustomEvent("itemdragleave", true, false, {
                                });
                                that.site.element.dispatchEvent(event);
                                that._firedDragEnter = false;
                            }
                            if (that._addedDragOverClass) {
                                that._addedDragOverClass = false;
                                _ElementUtilities.removeClass(that.site._element, _Constants._dragOverClass);
                            }
                            that._exitEventTimer = 0;
                            that._stopAutoScroll();
                        }
                    }, 40);
                },

                _getEventPositionInElementSpace: function (element, eventObject) {
                    var elementRect = { left: 0, top: 0 };
                    try {
                        elementRect = element.getBoundingClientRect();
                    }
                    catch (err) { }

                    var computedStyle = _Global.getComputedStyle(element, null),
                        paddingLeft = parseInt(computedStyle["paddingLeft"]),
                        paddingTop = parseInt(computedStyle["paddingTop"]),
                        borderLeft = parseInt(computedStyle["borderLeftWidth"]),
                        borderTop = parseInt(computedStyle["borderTopWidth"]),
                        clientX = eventObject.clientX,
                        clientY = eventObject.clientY;

                    var position = {
                        x: +clientX === clientX ? (clientX - elementRect.left - paddingLeft - borderLeft) : 0,
                        y: +clientY === clientY ? (clientY - elementRect.top - paddingTop - borderTop) : 0
                    };

                    if (this.site._rtl()) {
                        position.x = (elementRect.right - elementRect.left) - position.x;
                    }

                    return position;
                },

                _getPositionInCanvasSpace: function (eventObject) {
                    var scrollLeft = this.site._horizontal() ? this.site.scrollPosition : 0,
                        scrollTop = this.site._horizontal() ? 0 : this.site.scrollPosition,
                        position = this._getEventPositionInElementSpace(this.site.element, eventObject);

                    return {
                        x: position.x + scrollLeft,
                        y: position.y + scrollTop
                    };
                },

                _itemBeingDragged: function (itemIndex) {
                    if (!this._dragging) {
                        return false;
                    }

                    return ((this._draggingUnselectedItem && this._dragInfo._isIncluded(itemIndex)) || (!this._draggingUnselectedItem && this.site._isSelected(itemIndex)));
                },

                _addDragSourceClass: function (itemBox) {
                    this._draggedItemBoxes.push(itemBox);
                    _ElementUtilities.addClass(itemBox, _Constants._dragSourceClass);
                    if (itemBox.parentNode) {
                        _ElementUtilities.addClass(itemBox.parentNode, _Constants._footprintClass);
                    }
                },

                renderDragSourceOnRealizedItem: function (itemIndex, itemBox) {
                    if (this._itemBeingDragged(itemIndex)) {
                        this._addDragSourceClass(itemBox);
                    }
                },

                onDragOver: function (eventObject) {
                    if (!this._dragUnderstood) {
                        return;
                    }
                    this._pointerLeftRegion = false;
                    eventObject.preventDefault();

                    var cursorPositionInCanvas = this._getPositionInCanvasSpace(eventObject),
                        cursorPositionInRoot = this._getEventPositionInElementSpace(this.site.element, eventObject);
                    this._checkAutoScroll(cursorPositionInRoot.x, cursorPositionInRoot.y);
                    if (this.site._layout.hitTest) {
                        if (this._autoScrollFrame) {
                            if (this._lastInsertPoint) {
                                this.site._layout.dragLeave();
                                this._lastInsertPoint = null;
                            }
                        } else {
                            var insertPoint = this.site._view.hitTest(cursorPositionInCanvas.x, cursorPositionInCanvas.y);
                            insertPoint.insertAfterIndex = clampToRange(-1, this.site._cachedCount - 1, insertPoint.insertAfterIndex);
                            if (!this._lastInsertPoint || this._lastInsertPoint.insertAfterIndex !== insertPoint.insertAfterIndex || this._lastInsertPoint.index !== insertPoint.index) {
                                this._dragBetweenDisabled = !this._fireDragBetweenEvent(insertPoint.index, insertPoint.insertAfterIndex, eventObject.dataTransfer);
                                if (!this._dragBetweenDisabled) {
                                    this.site._layout.dragOver(cursorPositionInCanvas.x, cursorPositionInCanvas.y, this._dragInfo);
                                } else {
                                    this.site._layout.dragLeave();
                                }
                            }
                            this._lastInsertPoint = insertPoint;
                        }
                    }
                },

                _clearDragProperties: function () {
                    if (this._addedDragOverClass) {
                        this._addedDragOverClass = false;
                        _ElementUtilities.removeClass(this.site._element, _Constants._dragOverClass);
                    }
                    if (this._draggedItemBoxes) {
                        for (var i = 0, len = this._draggedItemBoxes.length; i < len; i++) {
                            _ElementUtilities.removeClass(this._draggedItemBoxes[i], _Constants._dragSourceClass);
                            if (this._draggedItemBoxes[i].parentNode) {
                                _ElementUtilities.removeClass(this._draggedItemBoxes[i].parentNode, _Constants._footprintClass);
                            }
                        }
                        this._draggedItemBoxes = [];
                    }
                    this.site._layout.dragLeave();
                    this._dragging = false;
                    this._dragInfo = null;
                    this._draggingUnselectedItem = false;
                    this._dragDataTransfer = null;
                    this._lastInsertPoint = null;
                    this._resetPointerDownState();
                    this._lastEnteredElement = null;
                    this._dragBetweenDisabled = false;
                    this._firedDragEnter = false;
                    this._dragUnderstood = false;
                    this._stopAutoScroll();
                },

                onDragEnd: function () {
                    var event = _Global.document.createEvent("CustomEvent");
                    event.initCustomEvent("itemdragend", true, false, {});
                    this.site.element.dispatchEvent(event);
                    this._clearDragProperties();
                },

                _findFirstAvailableInsertPoint: function (selectedItems, startIndex, searchForwards) {
                    var indicesSelected = selectedItems.getIndices(),
                        dropIndexInSelection = -1,
                        count = this.site._cachedCount,
                        selectionCount = indicesSelected.length,
                        startIndexInSelection = -1,
                        dropIndex = startIndex;
                    for (var i = 0; i < selectionCount; i++) {
                        if (indicesSelected[i] === dropIndex) {
                            dropIndexInSelection = i;
                            startIndexInSelection = i;
                            break;
                        }
                    }

                    while (dropIndexInSelection >= 0 && dropIndex >= 0) {
                        if (searchForwards) {
                            dropIndex++;
                            if (dropIndexInSelection < selectionCount && indicesSelected[dropIndexInSelection + 1] === dropIndex && dropIndex < count) {
                                dropIndexInSelection++;
                            } else if (dropIndex >= count) {
                                // If we hit the end of the list when looking for a new location ahead of our start index, it means everything from the starting point
                                // to the end is selected, so no valid index can be located to move the items. We need to start searching again, moving backwards
                                // from the starting location, to find the first available insert location to move the selected items.
                                searchForwards = false;
                                dropIndex = startIndex;
                                dropIndexInSelection = startIndexInSelection;
                            } else {
                                dropIndexInSelection = -1;
                            }
                        } else {
                            dropIndex--;
                            if (dropIndexInSelection > 0 && indicesSelected[dropIndexInSelection - 1] === dropIndex) {
                                dropIndexInSelection--;
                            } else {
                                dropIndexInSelection = -1;
                            }
                        }
                    }

                    return dropIndex;
                },

                _reorderItems: function (dropIndex, reorderedItems, reorderingUnselectedItem, useMoveBefore, ensureVisibleAtEnd) {
                    var site = this.site;
                    var updateSelection = function updatedSelectionOnDrop(items) {
                        // Update selection if the items were selected. If there is a range with length > 0 a move operation
                        // on the first or last item removes the range.
                        if (!reorderingUnselectedItem) {
                            site._selection.set({ firstKey: items[0].key, lastKey: items[items.length - 1].key });
                        } else {
                            site._selection.remove({ key: items[0].key });
                        }
                        if (ensureVisibleAtEnd) {
                            site.ensureVisible(site._selection._getFocused());
                        }
                    };
                    reorderedItems.getItems().then(function (items) {
                        var ds = site.itemDataSource;
                        if (dropIndex === -1) {
                            ds.beginEdits();
                            for (var i = items.length - 1; i >= 0; i--) {
                                ds.moveToStart(items[i].key);
                            }
                            ds.endEdits();
                            updateSelection(items);
                        } else {
                            var listBinding = ds.createListBinding();
                            listBinding.fromIndex(dropIndex).then(function (item) {
                                listBinding.release();
                                ds.beginEdits();
                                if (useMoveBefore) {
                                    for (var i = 0, len = items.length; i < len; i++) {
                                        ds.moveBefore(items[i].key, item.key);
                                    }
                                } else {
                                    for (var i = items.length - 1; i >= 0; i--) {
                                        ds.moveAfter(items[i].key, item.key);
                                    }
                                }
                                ds.endEdits();
                                updateSelection(items);
                            });
                        }
                    });
                },

                onDrop: function SelectionMode_onDrop(eventObject) {
                    // If the listview or the handler of the drop event we fire triggers a reorder, the dragged items can end up having different container nodes than what they started with.
                    // Because of that, we need to remove the footprint class from the item boxes' containers before we do any processing of the drop event.
                    if (this._draggedItemBoxes) {
                        for (var i = 0, len = this._draggedItemBoxes.length; i < len; i++) {
                            if (this._draggedItemBoxes[i].parentNode) {
                                _ElementUtilities.removeClass(this._draggedItemBoxes[i].parentNode, _Constants._footprintClass);
                            }
                        }
                    }
                    if (!this._dragBetweenDisabled) {
                        var cursorPosition = this._getPositionInCanvasSpace(eventObject);
                        var dropLocation = this.site._view.hitTest(cursorPosition.x, cursorPosition.y),
                            dropIndex = clampToRange(-1, this.site._cachedCount - 1, dropLocation.insertAfterIndex),
                            allowDrop = true;
                        // We don't fire dragBetween events during autoscroll, so if a user drops during autoscroll, we need to get up to date information
                        // on the drop location, and fire dragBetween before the insert so that the developer can prevent the drop if they choose.
                        if (!this._lastInsertPoint || this._lastInsertPoint.insertAfterIndex !== dropIndex || this._lastInsertPoint.index !== dropLocation.index) {
                            allowDrop = this._fireDragBetweenEvent(dropLocation.index, dropIndex, eventObject.dataTransfer);
                        }
                        if (allowDrop) {
                            this._lastInsertPoint = null;
                            this.site._layout.dragLeave();
                            if (this._fireDropEvent(dropLocation.index, dropIndex, eventObject.dataTransfer) && this._dragging && this.site.itemsReorderable) {
                                if (this._dragInfo.isEverything() || this.site._groupsEnabled()) {
                                    return;
                                }

                                dropIndex = this._findFirstAvailableInsertPoint(this._dragInfo, dropIndex, false);
                                this._reorderItems(dropIndex, this._dragInfo, this._draggingUnselectedItem);
                            }
                        }
                    }
                    this._clearDragProperties();
                    eventObject.preventDefault();
                },

                _checkAutoScroll: function (x, y) {
                    var viewportSize = this.site._getViewportLength(),
                        horizontal = this.site._horizontal(),
                        cursorPositionInViewport = (horizontal ? x : y),
                        canvasSize = this.site._viewport[horizontal ? "scrollWidth" : "scrollHeight"],
                        scrollPosition = Math.floor(this.site.scrollPosition),
                        travelRate = 0;

                    if (cursorPositionInViewport < _Constants._AUTOSCROLL_THRESHOLD) {
                        travelRate = cursorPositionInViewport - _Constants._AUTOSCROLL_THRESHOLD;
                    } else if (cursorPositionInViewport > (viewportSize - _Constants._AUTOSCROLL_THRESHOLD)) {
                        travelRate = (cursorPositionInViewport - (viewportSize - _Constants._AUTOSCROLL_THRESHOLD));
                    }
                    travelRate = Math.round((travelRate / _Constants._AUTOSCROLL_THRESHOLD) * (_Constants._MAX_AUTOSCROLL_RATE - _Constants._MIN_AUTOSCROLL_RATE));

                    // If we're at the edge of the content, we don't need to keep scrolling. We'll set travelRate to 0 to stop the autoscroll timer.
                    if ((scrollPosition === 0 && travelRate < 0) || (scrollPosition >= (canvasSize - viewportSize) && travelRate > 0)) {
                        travelRate = 0;
                    }
                    if (travelRate === 0) {
                        if (this._autoScrollDelay) {
                            _Global.clearTimeout(this._autoScrollDelay);
                            this._autoScrollDelay = 0;
                        }
                    } else {
                        if (!this._autoScrollDelay && !this._autoScrollFrame) {
                            var that = this;
                            this._autoScrollDelay = _Global.setTimeout(function () {
                                if (that._autoScrollRate) {
                                    that._lastDragTimeout = _BaseUtils._now();
                                    var nextFrame = function () {
                                        if ((!that._autoScrollRate && that._autoScrollFrame) || that.site._disposed) {
                                            that._stopAutoScroll();
                                        } else {
                                            // Timeout callbacks aren't reliably timed, so extra math is needed to figure out how far the scroll position should move since the last callback
                                            var currentTime = _BaseUtils._now();
                                            var delta = that._autoScrollRate * ((currentTime - that._lastDragTimeout) / 1000);
                                            delta = (delta < 0 ? Math.min(-1, delta) : Math.max(1, delta));
                                            var newScrollPos = {};
                                            newScrollPos[that.site._scrollProperty] = that.site._viewportScrollPosition + delta;
                                            _ElementUtilities.setScrollPosition(that.site._viewport, newScrollPos);
                                            that._lastDragTimeout = currentTime;
                                            that._autoScrollFrame = _BaseUtils._requestAnimationFrame(nextFrame);
                                        }
                                    };
                                    that._autoScrollFrame = _BaseUtils._requestAnimationFrame(nextFrame);
                                }
                            }, _Constants._AUTOSCROLL_DELAY);
                        }
                    }
                    this._autoScrollRate = travelRate;
                },

                _stopAutoScroll: function () {
                    if (this._autoScrollDelay) {
                        _Global.clearTimeout(this._autoScrollDelay);
                        this._autoScrollDelay = 0;
                    }
                    this._autoScrollRate = 0;
                    this._autoScrollFrame = 0;
                },

                onKeyDown: function SelectionMode_onKeyDown(eventObject) {
                    var that = this,
                        site = this.site,
                        swipeEnabled = site._swipeBehavior === _UI.SwipeBehavior.select,
                        view = site._view,
                        oldEntity = site._selection._getFocused(),
                        handled = true,
                        ctrlKeyDown = eventObject.ctrlKey;

                    function setNewFocus(newEntity, skipSelection, clampToBounds) {
                        function setNewFocusImpl(maxIndex) {
                            var moveView = true,
                                invalidIndex = false;
                            // Since getKeyboardNavigatedItem is purely geometry oriented, it can return us out of bounds numbers, so this check is necessary
                            if (clampToBounds) {
                                newEntity.index = Math.max(0, Math.min(maxIndex, newEntity.index));
                            } else if (newEntity.index < 0 || newEntity.index > maxIndex) {
                                invalidIndex = true;
                            }
                            if (!invalidIndex && (oldEntity.index !== newEntity.index || oldEntity.type !== newEntity.type)) {
                                var changeFocus = dispatchKeyboardNavigating(site._element, oldEntity, newEntity);
                                if (changeFocus) {
                                    moveView = false;

                                    // If the oldEntity is completely off-screen then we mimic the desktop
                                    // behavior. This is consistent with navbar keyboarding.
                                    if (that._setNewFocusItemOffsetPromise) {
                                        that._setNewFocusItemOffsetPromise.cancel();
                                    }
                                    site._batchViewUpdates(_Constants._ViewChange.realize, _Constants._ScrollToPriority.high, function () {
                                        that._setNewFocusItemOffsetPromise = site._getItemOffset(oldEntity, true).then(function (range) {
                                            range = site._convertFromCanvasCoordinates(range);
                                            var oldItemOffscreen = range.end <= site.scrollPosition || range.begin >= site.scrollPosition + site._getViewportLength() - 1;
                                            that._setNewFocusItemOffsetPromise = site._getItemOffset(newEntity).then(function (range) {
                                                that._setNewFocusItemOffsetPromise = null;
                                                var retVal = {
                                                    position: site.scrollPosition,
                                                    direction: "right"
                                                };
                                                if (oldItemOffscreen) {
                                                    // oldEntity is completely off-screen
                                                    site._selection._setFocused(newEntity, true);
                                                    range = site._convertFromCanvasCoordinates(range);
                                                    if (newEntity.index > oldEntity.index) {
                                                        retVal.direction = "right";
                                                        retVal.position = range.end - site._getViewportLength();
                                                    } else {
                                                        retVal.direction = "left";
                                                        retVal.position = range.begin;
                                                    }
                                                }
                                                site._changeFocus(newEntity, skipSelection, ctrlKeyDown, oldItemOffscreen, true);
                                                if (!oldItemOffscreen) {
                                                    return Promise.cancel;
                                                } else {
                                                    return retVal;
                                                }
                                            }, function (error) {
                                                site._changeFocus(newEntity, skipSelection, ctrlKeyDown, true, true);
                                                return Promise.wrapError(error);
                                            });
                                            return that._setNewFocusItemOffsetPromise;
                                        }, function (error) {
                                            site._changeFocus(newEntity, skipSelection, ctrlKeyDown, true, true);
                                            return Promise.wrapError(error);
                                        });
                                        return that._setNewFocusItemOffsetPromise;
                                    }, true);
                                }
                            }
                            // When a key is pressed, we want to make sure the current focus is in view. If the keypress is changing to a new valid index,
                            // _changeFocus will handle moving the viewport for us. If the focus isn't moving, though, we need to put the view back on
                            // the current item ourselves and call setFocused(oldFocus, true) to make sure that the listview knows the focused item was
                            // focused via keyboard and renders the rectangle appropriately.
                            if (moveView) {
                                site._selection._setFocused(oldEntity, true);
                                site.ensureVisible(oldEntity);
                            }
                            if (invalidIndex) {
                                return { type: _UI.ObjectType.item, index: _Constants._INVALID_INDEX };
                            } else {
                                return newEntity;
                            }
                        }

                        // We need to get the final item in the view so that we don't try setting focus out of bounds.
                        if (newEntity.type !== _UI.ObjectType.groupHeader) {
                            return view.finalItem().then(setNewFocusImpl);
                        } else {
                            return Promise.wrap(site._groups.length() - 1).then(setNewFocusImpl);
                        }
                    }

                    var Key = _ElementUtilities.Key,
                        keyCode = eventObject.keyCode,
                        rtl = site._rtl();

                    if (!this._isInteractive(eventObject.target)) {
                        if (eventObject.ctrlKey && !eventObject.altKey && !eventObject.shiftKey && this._keyboardAcceleratorHandlers[keyCode]) {
                            this._keyboardAcceleratorHandlers[keyCode]();
                        }
                        if (site.itemsReorderable && (!eventObject.ctrlKey && eventObject.altKey && eventObject.shiftKey && oldEntity.type === _UI.ObjectType.item) &&
                            (keyCode === Key.leftArrow || keyCode === Key.rightArrow || keyCode === Key.upArrow || keyCode === Key.downArrow)) {
                            var selection = site._selection,
                                focusedIndex = oldEntity.index,
                                movingUnselectedItem = false,
                                processReorder = true;
                            if (!selection.isEverything()) {
                                if (!selection._isIncluded(focusedIndex)) {
                                    var item = site._view.items.itemAt(focusedIndex);
                                    // Selected items should never be marked as non draggable, so we only need to check for nonDraggableClass when trying to reorder an unselected item.
                                    if (item && _ElementUtilities.hasClass(item, _Constants._nonDraggableClass)) {
                                        processReorder = false;
                                    } else {
                                        movingUnselectedItem = true;
                                        selection = new _SelectionManager._Selection(this.site, [{ firstIndex: focusedIndex, lastIndex: focusedIndex }]);
                                    }
                                }
                                if (processReorder) {
                                    var dropIndex = focusedIndex;
                                    if (keyCode === Key.rightArrow) {
                                        dropIndex += (rtl ? -1 : 1);
                                    } else if (keyCode === Key.leftArrow) {
                                        dropIndex += (rtl ? 1 : -1);
                                    } else if (keyCode === Key.upArrow) {
                                        dropIndex--;
                                    } else {
                                        dropIndex++;
                                    }
                                    // If the dropIndex is larger than the original index, we're trying to move items forward, so the search for the first unselected item to insert after should move forward.
                                    var movingAhead = (dropIndex > focusedIndex),
                                        searchForward = movingAhead;
                                    if (movingAhead && dropIndex >= this.site._cachedCount) {
                                        // If we're at the end of the list and trying to move items forward, dropIndex should be >= cachedCount.
                                        // That doesn't mean we don't have to do any reordering, though. A selection could be broken down into
                                        // a few blocks. We need to make the selection contiguous after this reorder, so we've got to search backwards
                                        // to find the first unselected item, then move everything in the selection after it.
                                        searchForward = false;
                                        dropIndex = this.site._cachedCount - 1;
                                    }
                                    dropIndex = this._findFirstAvailableInsertPoint(selection, dropIndex, searchForward);
                                    dropIndex = Math.min(Math.max(-1, dropIndex), this.site._cachedCount - 1);
                                    var reportedInsertAfterIndex = dropIndex - (movingAhead || dropIndex === -1 ? 0 : 1),
                                        reportedIndex = dropIndex,
                                        groupsEnabled = this.site._groupsEnabled();

                                    if (groupsEnabled) {
                                        // The indices we picked for the index/insertAfterIndex to report in our events is always correct in an ungrouped list,
                                        // and mostly correct in a grouped list. The only problem occurs when you move an item (or items) ahead into a new group,
                                        // or back into a previous group, such that the items should be the first/last in the group. Take this list as an example:
                                        // [Group A] [a] [b] [c] [Group B] [d] [e]
                                        // When [d] is focused, right/down arrow reports index: 4, insertAfterIndex: 4, which is right -- it means move [d] after [e].
                                        // Similarily, when [c] is focused and left/up is pressed, we report index: 1, insertAfterIndex: 0 -- move [c] to after [a].
                                        // Take note that index does not tell us where focus is / what item is being moved.
                                        // Like mouse/touch DnD, index tells us what the dragBetween slots would be were we to animate a dragBetween.
                                        // The problem cases are moving backwards into a previous group, or forward into the next group.
                                        // If [c] were focused and the user pressed right/down, we would report index: 3, insertAfterIndex: 3. In other words, move [c] after [d].
                                        // That's not right at all - [c] needs to become the first element of [Group B]. When we're moving ahead, then, and our dropIndex
                                        // is the first index of a new group, we adjust insertAfterIndex to be dropIndex - 1. Now we'll report index:3, insertAfterIndex: 2, which means
                                        // [c] is now the first element of [Group B], rather than the last element of [Group A]. This is exactly the same as what we would report when
                                        // the user mouse/touch drags [c] right before [d].
                                        // Similarily, when [d] is focused and we press left/up, without the logic below we would report index: 2, insertAfterIndex: 1, so we'd try to move
                                        // [d] ahead of [b]. Again, [d] first needs the opportunity to become the last element in [Group A], so we adjust the insertAfterIndex up by 1.
                                        // We then will report index:2, insertAfterIndex:2, meaning insert [d] in [Group A] after [c], which again mimics the mouse/touch API.
                                        var groups = this.site._groups,
                                            groupIndex = (dropIndex > -1 ? groups.groupFromItem(dropIndex) : 0);
                                        if (movingAhead) {
                                            if (groups.group(groupIndex).startIndex === dropIndex) {
                                                reportedInsertAfterIndex--;
                                            }
                                        } else if (groupIndex < (groups.length() - 1) && dropIndex === (groups.group(groupIndex + 1).startIndex - 1)) {
                                            reportedInsertAfterIndex++;
                                        }
                                    }

                                    if (this._fireDragBetweenEvent(reportedIndex, reportedInsertAfterIndex, null) && this._fireDropEvent(reportedIndex, reportedInsertAfterIndex, null)) {
                                        if (groupsEnabled) {
                                            return;
                                        }

                                        this._reorderItems(dropIndex, selection, movingUnselectedItem, !movingAhead, true);
                                    }
                                }
                            }
                        } else if (!eventObject.altKey) {

                            if (this._keyboardNavigationHandlers[keyCode]) {
                                this._keyboardNavigationHandlers[keyCode](oldEntity).then(function (newEntity) {
                                    var clampToBounds = that._keyboardNavigationHandlers[keyCode].clampToBounds;
                                    if (newEntity.type !== _UI.ObjectType.groupHeader && eventObject.shiftKey && site._selectionAllowed() && site._multiSelection()) {
                                        // Shift selection should work when shift or shift+ctrl are depressed
                                        if (site._selection._pivot === _Constants._INVALID_INDEX) {
                                            site._selection._pivot = oldEntity.index;
                                        }
                                        setNewFocus(newEntity, true, clampToBounds).then(function (newEntity) {
                                            if (newEntity.index !== _Constants._INVALID_INDEX) {
                                                var firstIndex = Math.min(newEntity.index, site._selection._pivot),
                                                    lastIndex = Math.max(newEntity.index, site._selection._pivot),
                                                    additive = (eventObject.ctrlKey || site._tap === _UI.TapBehavior.toggleSelect);
                                                that._selectRange(firstIndex, lastIndex, additive);
                                            }
                                        });
                                    } else {
                                        site._selection._pivot = _Constants._INVALID_INDEX;
                                        setNewFocus(newEntity, false, clampToBounds);
                                    }
                                });
                            } else if (!eventObject.ctrlKey && keyCode === Key.enter) {
                                var element = oldEntity.type === _UI.ObjectType.groupHeader ? site._groups.group(oldEntity.index).header : site._view.items.itemBoxAt(oldEntity.index);
                                if (element) {
                                    if (oldEntity.type === _UI.ObjectType.groupHeader) {
                                        this._pressedHeader = element;
                                        this._pressedItemBox = null;
                                        this._pressedContainer = null;
                                    } else {
                                        this._pressedItemBox = element;
                                        this._pressedContainer = site._view.items.containerAt(oldEntity.index);
                                        this._pressedHeader = null;
                                    }

                                    var allowed = this._verifySelectionAllowed(oldEntity);
                                    if (allowed.canTapSelect) {
                                        this._itemEventsHandler.handleTap(oldEntity);
                                    }
                                    this._fireInvokeEvent(oldEntity, element);
                                }
                            } else if (oldEntity.type !== _UI.ObjectType.groupHeader &&
                                    ((eventObject.ctrlKey && keyCode === Key.enter) ||
                                    (swipeEnabled && eventObject.shiftKey && keyCode === Key.F10) ||
                                    (swipeEnabled && keyCode === Key.menu) ||
                                    keyCode === Key.space)) {
                                // Swipe emulation
                                this._itemEventsHandler.handleSwipeBehavior(oldEntity.index);
                                site._changeFocus(oldEntity, true, ctrlKeyDown, false, true);
                            } else if (keyCode === Key.escape && site._selection.count() > 0) {
                                site._selection._pivot = _Constants._INVALID_INDEX;
                                site._selection.clear();
                            } else {
                                handled = false;
                            }
                        } else {
                            handled = false;
                        }

                        this._keyDownHandled = handled;
                        if (handled) {
                            eventObject.stopPropagation();
                            eventObject.preventDefault();
                        }
                    }

                    if (keyCode === Key.tab) {
                        this.site._keyboardFocusInbound = true;
                    }
                },

                onKeyUp: function (eventObject) {
                    if (this._keyDownHandled) {
                        eventObject.stopPropagation();
                        eventObject.preventDefault();
                    }
                },

                onTabEntered: function (eventObject) {
                    if (this.site._groups.length() === 0) {
                        return;
                    }

                    var site = this.site,
                        focused = site._selection._getFocused(),
                        forward = eventObject.detail;

                    // We establish whether focus is incoming on the ListView by checking keyboard focus and the srcElement.
                    // If the ListView did not have keyboard focus, then it is definitely incoming since keyboard focus is cleared
                    // on blur which works for 99% of all scenarios. When the ListView is the only tabbable element on the page,
                    // then tabbing out of the ListView will make focus wrap around and focus the ListView again. The blur event is
                    // handled after TabEnter, so the keyboard focus flag is not yet cleared. Therefore, we examine the srcElement and see
                    // if it is the _viewport since it is the first tabbable element in the ListView DOM tree.
                    var inboundFocus = !site._hasKeyboardFocus || eventObject.target === site._viewport;
                    if (inboundFocus) {
                        this.inboundFocusHandled = true;

                        // We tabbed into the ListView
                        focused.index = (focused.index === _Constants._INVALID_INDEX ? 0 : focused.index);

                        if (forward || !this.site._supportsGroupHeaderKeyboarding) {
                            // We tabbed into the ListView from before the ListView, so focus should go to items
                            var entity = { type: _UI.ObjectType.item };
                            if (focused.type === _UI.ObjectType.groupHeader) {
                                entity.index = site._groupFocusCache.getIndexForGroup(focused.index);
                                if (dispatchKeyboardNavigating(site._element, focused, entity)) {
                                    site._changeFocus(entity, true, false, false, true);
                                } else {
                                    site._changeFocus(focused, true, false, false, true);
                                }
                            } else {
                                entity.index = focused.index;
                                site._changeFocus(entity, true, false, false, true);
                            }
                            eventObject.preventDefault();
                        } else {
                            // We tabbed into the ListView from after the ListView, focus should go to headers
                            var entity = { type: _UI.ObjectType.groupHeader };
                            if (focused.type !== _UI.ObjectType.groupHeader) {
                                entity.index = site._groups.groupFromItem(focused.index);
                                if (dispatchKeyboardNavigating(site._element, focused, entity)) {
                                    site._changeFocus(entity, true, false, false, true);
                                } else {
                                    site._changeFocus(focused, true, false, false, true);
                                }
                            } else {
                                entity.index = focused.index;
                                site._changeFocus(entity, true, false, false, true);
                            }
                            eventObject.preventDefault();
                        }
                    }
                },

                onTabExiting: function (eventObject) {
                    if (!this.site._supportsGroupHeaderKeyboarding || this.site._groups.length() === 0) {
                        return;
                    }

                    var site = this.site,
                        focused = site._selection._getFocused(),
                        forward = eventObject.detail;

                    if (forward && focused.type !== _UI.ObjectType.groupHeader) {
                        // Tabbing and we were focusing an item, go to headers
                        var entity = { type: _UI.ObjectType.groupHeader, index: site._groups.groupFromItem(focused.index) };
                        if (dispatchKeyboardNavigating(site._element, focused, entity)) {
                            site._changeFocus(entity, true, false, false, true);
                            eventObject.preventDefault();
                        }
                    } else if (!forward && focused.type === _UI.ObjectType.groupHeader) {
                        // Shift tabbing and we were focusing a header, go to items
                        var entity = { type: _UI.ObjectType.item, index: site._groupFocusCache.getIndexForGroup(focused.index) };
                        if (dispatchKeyboardNavigating(site._element, focused, entity)) {
                            site._changeFocus(entity, true, false, false, true);
                            eventObject.preventDefault();
                        }
                    }
                }
            });
            return _SelectionMode;
        })
    });

});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/ListView/_ErrorMessages',[
        'exports',
        '../../Core/_Base',
        '../../Core/_Resources'
    ], function errorMessagesInit(exports, _Base, _Resources) {
    "use strict";

    _Base.Namespace._moduleDefine(exports, null, {

        modeIsInvalid: {
            get: function () { return "Invalid argument: mode must be one of following values: 'none', 'single' or 'multi'."; }
        },

        loadingBehaviorIsDeprecated: {
            get: function () { return "Invalid configuration: loadingBehavior is deprecated. The control will default this property to 'randomAccess'. Please refer to the 'ListView loading behaviors' SDK Sample for guidance on how to implement incremental load behavior."; }
        },

        pagesToLoadIsDeprecated: {
            get: function () { return "Invalid configuration: pagesToLoad is deprecated. The control will not use this property. Please refer to the 'ListView loading behaviors' SDK Sample for guidance on how to implement incremental load behavior."; }
        },

        pagesToLoadThresholdIsDeprecated: {
            get: function () { return "Invalid configuration: pagesToLoadThreshold is deprecated.  The control will not use this property. Please refer to the 'ListView loading behaviors' SDK Sample for guidance on how to implement incremental load behavior."; }
        },

        automaticallyLoadPagesIsDeprecated: {
            get: function () { return "Invalid configuration: automaticallyLoadPages is deprecated. The control will default this property to false. Please refer to the 'ListView loading behaviors' SDK Sample for guidance on how to implement incremental load behavior."; }
        },

        invalidTemplate: {
            get: function () { return "Invalid template: Templates must be created before being passed to the ListView, and must contain a valid tree of elements."; }
        },

        loadMorePagesIsDeprecated: {
            get: function () { return "loadMorePages is deprecated. Invoking this function will not have any effect. Please refer to the 'ListView loading behaviors' SDK Sample for guidance on how to implement incremental load behavior."; }
        },

        disableBackdropIsDeprecated: {
            get: function () { return "Invalid configuration: disableBackdrop is deprecated. Style: .win-listview .win-container.win-backdrop { background-color:transparent; } instead."; }
        },

        backdropColorIsDeprecated: {
            get: function () { return "Invalid configuration: backdropColor is deprecated. Style: .win-listview .win-container.win-backdrop { rgba(155,155,155,0.23); } instead."; }
        },

        itemInfoIsDeprecated: {
            get: function () { return "GridLayout.itemInfo may be altered or unavailable in future versions. Instead, use CellSpanningLayout."; }
        },

        groupInfoIsDeprecated: {
            get: function () { return "GridLayout.groupInfo may be altered or unavailable in future versions. Instead, use CellSpanningLayout."; }
        },

        resetItemIsDeprecated: {
            get: function () { return "resetItem may be altered or unavailable in future versions. Instead, mark the element as disposable using WinJS.Utilities.markDisposable."; }
        },

        resetGroupHeaderIsDeprecated: {
            get: function () { return "resetGroupHeader may be altered or unavailable in future versions. Instead, mark the header element as disposable using WinJS.Utilities.markDisposable."; }
        },

        maxRowsIsDeprecated: {
            get: function () { return "GridLayout.maxRows may be altered or unavailable in future versions. Instead, use the maximumRowsOrColumns property."; }
        }
    });

});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/ListView/_GroupFocusCache',[
    'exports',
    '../../Core/_Base'
    ], function GroupFocusCacheInit(exports, _Base) {
    "use strict";

    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {
        _GroupFocusCache: _Base.Namespace._lazy(function () {
            return _Base.Class.define(function GroupFocusCache_ctor(listView) {
                this._listView = listView;
                this.clear();
            }, {
                // We store indices as strings in the cache so index=0 does not evaluate to false as
                // when we check for the existance of an index in the cache. The index is converted
                // back into a number when calling getIndexForGroup

                updateCache: function (groupKey, itemKey, itemIndex) {
                    itemIndex = "" + itemIndex;
                    this._itemToIndex[itemKey] = itemIndex;
                    this._groupToItem[groupKey] = itemKey;
                },

                deleteItem: function (itemKey) {
                    if (!this._itemToIndex[itemKey]) {
                        return;
                    }

                    var that = this;
                    var keys = Object.keys(this._groupToItem);
                    for (var i = 0, len = keys.length; i < len; i++) {
                        var key = keys[i];
                        if (that._groupToItem[key] === itemKey) {
                            that.deleteGroup(key);
                            break;
                        }
                    }
                },

                deleteGroup: function (groupKey) {
                    var itemKey = this._groupToItem[groupKey];
                    if (itemKey) {
                        delete this._itemToIndex[itemKey];
                    }
                    delete this._groupToItem[groupKey];
                },

                updateItemIndex: function (itemKey, itemIndex) {
                    if (!this._itemToIndex[itemKey]) {
                        return;
                    }
                    this._itemToIndex[itemKey] = "" + itemIndex;
                },

                getIndexForGroup: function (groupIndex) {
                    var groupKey = this._listView._groups.group(groupIndex).key;

                    var itemKey = this._groupToItem[groupKey];
                    if (itemKey && this._itemToIndex[itemKey]) {
                        return +this._itemToIndex[itemKey];
                    } else {
                        return this._listView._groups.fromKey(groupKey).group.startIndex;
                    }
                },

                clear: function () {
                    this._groupToItem = {};
                    this._itemToIndex = {};
                }
            });
        }),

        _UnsupportedGroupFocusCache: _Base.Namespace._lazy(function () {
            return _Base.Class.define(null, {
                updateCache: function () {
                },

                deleteItem: function () {
                },

                deleteGroup: function () {
                },

                updateItemIndex: function () {
                },

                getIndexForGroup: function () {
                    return 0;
                },

                clear: function () {
                }
            });
        })
    });

});
// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/ListView/_GroupsContainer',[
    'exports',
    '../../Core/_Base',
    '../../Promise',
    '../../Utilities/_Dispose',
    '../../Utilities/_ElementUtilities',
    '../../Utilities/_ItemsManager',
    '../../Utilities/_UI',
    '../ItemContainer/_Constants'
    ], function groupsContainerInit(exports, _Base, Promise, _Dispose, _ElementUtilities, _ItemsManager, _UI, _Constants) {
    "use strict";

    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {
        _GroupsContainerBase: _Base.Namespace._lazy(function () {
            return _Base.Class.define(function () {
            }, {
                index: function (element) {
                    var header = this.headerFrom(element);
                    if (header) {
                        for (var i = 0, len = this.groups.length; i < len; i++) {
                            if (header === this.groups[i].header) {
                                return i;
                            }
                        }
                    }
                    return _Constants._INVALID_INDEX;
                },

                headerFrom: function (element) {
                    while (element && !_ElementUtilities.hasClass(element, _Constants._headerClass)) {
                        element = element.parentNode;
                    }
                    return element;
                },

                requestHeader: function GroupsContainerBase_requestHeader(index) {
                    this._waitingHeaderRequests = this._waitingHeaderRequests || {};
                    if (!this._waitingHeaderRequests[index]) {
                        this._waitingHeaderRequests[index] = [];
                    }

                    var that = this;
                    return new Promise(function (complete) {
                        var group = that.groups[index];
                        if (group && group.header) {
                            complete(group.header);
                        } else {
                            that._waitingHeaderRequests[index].push(complete);
                        }
                    });
                },

                notify: function GroupsContainerBase_notify(index, header) {
                    if (this._waitingHeaderRequests && this._waitingHeaderRequests[index]) {
                        var requests = this._waitingHeaderRequests[index];
                        for (var i = 0, len = requests.length; i < len; i++) {
                            requests[i](header);
                        }

                        this._waitingHeaderRequests[index] = [];
                    }
                },

                groupFromImpl: function GroupsContainerBase_groupFromImpl(fromGroup, toGroup, comp) {
                    if (toGroup < fromGroup) {
                        return null;
                    }

                    var center = fromGroup + Math.floor((toGroup - fromGroup) / 2),
                        centerGroup = this.groups[center];

                    if (comp(centerGroup, center)) {
                        return this.groupFromImpl(fromGroup, center - 1, comp);
                    } else if (center < toGroup && !comp(this.groups[center + 1], center + 1)) {
                        return this.groupFromImpl(center + 1, toGroup, comp);
                    } else {
                        return center;
                    }
                },

                groupFrom: function GroupsContainerBase_groupFrom(comp) {
                    if (this.groups.length > 0) {
                        var lastGroupIndex = this.groups.length - 1,
                            lastGroup = this.groups[lastGroupIndex];

                        if (!comp(lastGroup, lastGroupIndex)) {
                            return lastGroupIndex;
                        } else {
                            return this.groupFromImpl(0, this.groups.length - 1, comp);
                        }
                    } else {
                        return null;
                    }
                },

                groupFromItem: function GroupsContainerBase_groupFromItem(itemIndex) {
                    return this.groupFrom(function (group) {
                        return itemIndex < group.startIndex;
                    });
                },

                groupFromOffset: function GroupsContainerBase_groupFromOffset(offset) {
                    return this.groupFrom(function (group) {
                        return offset < group.offset;
                    });
                },

                group: function GroupsContainerBase_getGroup(index) {
                    return this.groups[index];
                },

                length: function GroupsContainerBase_length() {
                    return this.groups.length;
                },

                cleanUp: function GroupsContainerBase_cleanUp() {
                    if (this.listBinding) {
                        for (var i = 0, len = this.groups.length; i < len; i++) {
                            var group = this.groups[i];
                            if (group.userData) {
                                this.listBinding.releaseItem(group.userData);
                            }
                        }
                        this.listBinding.release();
                    }
                },

                _dispose: function GroupsContainerBase_dispose() {
                    this.cleanUp();
                },

                synchronizeGroups: function GroupsContainerBase_synchronizeGroups() {
                    var that = this;

                    this.pendingChanges = [];
                    this.ignoreChanges = true;
                    return this.groupDataSource.invalidateAll().then(function () {
                        return Promise.join(that.pendingChanges);
                    }).then(function () {
                        if (that._listView._ifZombieDispose()) {
                            return Promise.cancel;
                        }
                    }).then(
                        function () {
                            that.ignoreChanges = false;
                        },
                        function (error) {
                            that.ignoreChanges = false;
                            return Promise.wrapError(error);
                        }
                    );
                },

                fromKey: function GroupsContainerBase_fromKey(key) {
                    for (var i = 0, len = this.groups.length; i < len; i++) {
                        var group = this.groups[i];
                        if (group.key === key) {
                            return {
                                group: group,
                                index: i
                            };
                        }
                    }
                    return null;
                },

                fromHandle: function GroupsContainerBase_fromHandle(handle) {
                    for (var i = 0, len = this.groups.length; i < len; i++) {
                        var group = this.groups[i];
                        if (group.handle === handle) {
                            return {
                                group: group,
                                index: i
                            };
                        }
                    }
                    return null;
                }
            });
        }),

        _UnvirtualizedGroupsContainer: _Base.Namespace._lazy(function () {
            return _Base.Class.derive(exports._GroupsContainerBase, function (listView, groupDataSource) {
                this._listView = listView;
                this.groupDataSource = groupDataSource;
                this.groups = [];
                this.pendingChanges = [];
                this.dirty = true;

                var that = this,
                notificationHandler = {
                    beginNotifications: function GroupsContainer_beginNotifications() {
                        that._listView._versionManager.beginNotifications();
                    },

                    endNotifications: function GroupsContainer_endNotifications() {
                        that._listView._versionManager.endNotifications();

                        if (that._listView._ifZombieDispose()) { return; }

                        if (!that.ignoreChanges && that._listView._groupsChanged) {
                            that._listView._scheduleUpdate();
                        }
                    },

                    indexChanged: function GroupsContainer_indexChanged() {
                        that._listView._versionManager.receivedNotification();

                        if (that._listView._ifZombieDispose()) { return; }

                        this.scheduleUpdate();
                    },

                    itemAvailable: function GroupsContainer_itemAvailable() {
                    },

                    countChanged: function GroupsContainer_countChanged(newCount) {
                        that._listView._versionManager.receivedNotification();

                        that._listView._writeProfilerMark("groupCountChanged(" + newCount + "),info");

                        if (that._listView._ifZombieDispose()) { return; }

                        this.scheduleUpdate();
                    },

                    changed: function GroupsContainer_changed(newItem) {
                        that._listView._versionManager.receivedNotification();

                        if (that._listView._ifZombieDispose()) { return; }

                        var groupEntry = that.fromKey(newItem.key);
                        if (groupEntry) {
                            that._listView._writeProfilerMark("groupChanged(" + groupEntry.index + "),info");

                            groupEntry.group.userData = newItem;
                            groupEntry.group.startIndex = newItem.firstItemIndexHint;
                            this.markToRemove(groupEntry.group);
                        }

                        this.scheduleUpdate();
                    },

                    removed: function GroupsContainer_removed(itemHandle) {
                        that._listView._versionManager.receivedNotification();
                        that._listView._groupRemoved(itemHandle);

                        if (that._listView._ifZombieDispose()) { return; }

                        var groupEntry = that.fromHandle(itemHandle);
                        if (groupEntry) {
                            that._listView._writeProfilerMark("groupRemoved(" + groupEntry.index + "),info");

                            that.groups.splice(groupEntry.index, 1);
                            var index = that.groups.indexOf(groupEntry.group, groupEntry.index);

                            if (index > -1) {
                                that.groups.splice(index, 1);
                            }

                            this.markToRemove(groupEntry.group);
                        }

                        this.scheduleUpdate();
                    },

                    inserted: function GroupsContainer_inserted(itemPromise, previousHandle, nextHandle) {
                        that._listView._versionManager.receivedNotification();

                        if (that._listView._ifZombieDispose()) { return; }

                        that._listView._writeProfilerMark("groupInserted,info");

                        var notificationHandler = this;
                        itemPromise.retain().then(function (item) {

                            var index;
                            if (!previousHandle && !nextHandle && !that.groups.length) {
                                index = 0;
                            } else {
                                index = notificationHandler.findIndex(previousHandle, nextHandle);
                            }
                            if (index !== -1) {
                                var newGroup = {
                                    key: item.key,
                                    startIndex: item.firstItemIndexHint,
                                    userData: item,
                                    handle: itemPromise.handle
                                };

                                that.groups.splice(index, 0, newGroup);
                            }
                            notificationHandler.scheduleUpdate();
                        });
                        that.pendingChanges.push(itemPromise);
                    },

                    moved: function GroupsContainer_moved(itemPromise, previousHandle, nextHandle) {
                        that._listView._versionManager.receivedNotification();

                        if (that._listView._ifZombieDispose()) { return; }

                        that._listView._writeProfilerMark("groupMoved,info");

                        var notificationHandler = this;
                        itemPromise.then(function (item) {
                            var newIndex = notificationHandler.findIndex(previousHandle, nextHandle),
                                groupEntry = that.fromKey(item.key);

                            if (groupEntry) {
                                that.groups.splice(groupEntry.index, 1);

                                if (newIndex !== -1) {
                                    if (groupEntry.index < newIndex) {
                                        newIndex--;
                                    }

                                    groupEntry.group.key = item.key;
                                    groupEntry.group.userData = item;
                                    groupEntry.group.startIndex = item.firstItemIndexHint;
                                    that.groups.splice(newIndex, 0, groupEntry.group);
                                }

                            } else if (newIndex !== -1) {
                                var newGroup = {
                                    key: item.key,
                                    startIndex: item.firstItemIndexHint,
                                    userData: item,
                                    handle: itemPromise.handle
                                };
                                that.groups.splice(newIndex, 0, newGroup);
                                itemPromise.retain();
                            }

                            notificationHandler.scheduleUpdate();
                        });
                        that.pendingChanges.push(itemPromise);
                    },

                    reload: function GroupsContainer_reload() {
                        that._listView._versionManager.receivedNotification();

                        if (that._listView._ifZombieDispose()) {
                            return;
                        }

                        that._listView._processReload();
                    },

                    markToRemove: function GroupsContainer_markToRemove(group) {
                        if (group.header) {
                            var header = group.header;
                            group.header = null;
                            group.left = -1;
                            group.width = -1;
                            group.decorator = null;
                            group.tabIndex = -1;
                            header.tabIndex = -1;

                            that._listView._groupsToRemove[_ElementUtilities._uniqueID(header)] = { group: group, header: header };
                        }
                    },

                    scheduleUpdate: function GroupsContainer_scheduleUpdate() {
                        that.dirty = true;
                        if (!that.ignoreChanges) {
                            that._listView._groupsChanged = true;
                        }
                    },

                    findIndex: function GroupsContainer_findIndex(previousHandle, nextHandle) {
                        var index = -1,
                            groupEntry;

                        if (previousHandle) {
                            groupEntry = that.fromHandle(previousHandle);
                            if (groupEntry) {
                                index = groupEntry.index + 1;
                            }
                        }

                        if (index === -1 && nextHandle) {
                            groupEntry = that.fromHandle(nextHandle);
                            if (groupEntry) {
                                index = groupEntry.index;
                            }
                        }

                        return index;
                    },

                    removeElements: function GroupsContainer_removeElements(group) {
                        if (group.header) {
                            var parentNode = group.header.parentNode;
                            if (parentNode) {
                                _Dispose.disposeSubTree(group.header);
                                parentNode.removeChild(group.header);
                            }
                            group.header = null;
                            group.left = -1;
                            group.width = -1;
                        }
                    }
                };

                this.listBinding = this.groupDataSource.createListBinding(notificationHandler);
            }, {
                initialize: function UnvirtualizedGroupsContainer_initialize() {
                    if (this.initializePromise) {
                        this.initializePromise.cancel();
                    }

                    this._listView._writeProfilerMark("GroupsContainer_initialize,StartTM");

                    var that = this;
                    this.initializePromise = this.groupDataSource.getCount().then(function (count) {
                        var promises = [];
                        for (var i = 0; i < count; i++) {
                            promises.push(that.listBinding.fromIndex(i).retain());
                        }
                        return Promise.join(promises);
                    }).then(
                        function (groups) {
                            that.groups = [];

                            for (var i = 0, len = groups.length; i < len; i++) {
                                var group = groups[i];

                                that.groups.push({
                                    key: group.key,
                                    startIndex: group.firstItemIndexHint,
                                    handle: group.handle,
                                    userData: group,
                                });
                            }
                            that._listView._writeProfilerMark("GroupsContainer_initialize groups(" + groups.length + "),info");
                            that._listView._writeProfilerMark("GroupsContainer_initialize,StopTM");
                        },
                        function (error) {
                            that._listView._writeProfilerMark("GroupsContainer_initialize,StopTM");
                            return Promise.wrapError(error);
                        });
                    return this.initializePromise;
                },

                renderGroup: function UnvirtualizedGroupsContainer_renderGroup(index) {
                    if (this._listView.groupHeaderTemplate) {
                        var group = this.groups[index];
                        return Promise.wrap(this._listView._groupHeaderRenderer(Promise.wrap(group.userData))).then(_ItemsManager._normalizeRendererReturn);
                    } else {
                        return Promise.wrap(null);
                    }
                },

                setDomElement: function UnvirtualizedGroupsContainer_setDomElement(index, headerElement) {
                    this.groups[index].header = headerElement;
                    this.notify(index, headerElement);
                },

                removeElements: function UnvirtualizedGroupsContainer_removeElements() {
                    var elements = this._listView._groupsToRemove || {},
                        keys = Object.keys(elements),
                        focusedItemPurged = false;

                    var focused = this._listView._selection._getFocused();
                    for (var i = 0, len = keys.length; i < len; i++) {
                        var group = elements[keys[i]],
                            header = group.header,
                            groupData = group.group;

                        if (!focusedItemPurged && focused.type === _UI.ObjectType.groupHeader && groupData.userData.index === focused.index) {
                            this._listView._unsetFocusOnItem();
                            focusedItemPurged = true;
                        }

                        if (header) {
                            var parentNode = header.parentNode;
                            if (parentNode) {
                                _Dispose._disposeElement(header);
                                parentNode.removeChild(header);
                            }
                        }
                    }

                    if (focusedItemPurged) {
                        this._listView._setFocusOnItem(focused);
                    }

                    this._listView._groupsToRemove = {};
                },

                resetGroups: function UnvirtualizedGroupsContainer_resetGroups() {
                    var groups = this.groups.slice(0);

                    for (var i = 0, len = groups.length; i < len; i++) {
                        var group = groups[i];

                        if (this.listBinding && group.userData) {
                            this.listBinding.releaseItem(group.userData);
                        }
                    }

                    // Set the lengths to zero to clear the arrays, rather than setting = [], which re-instantiates
                    this.groups.length = 0;
                    this.dirty = true;
                }
            });
        }),

        _NoGroups: _Base.Namespace._lazy(function () {
            return _Base.Class.derive(exports._GroupsContainerBase, function (listView) {
                this._listView = listView;
                this.groups = [{ startIndex: 0 }];
                this.dirty = true;
            }, {
                synchronizeGroups: function () {
                    return Promise.wrap();
                },

                addItem: function () {
                    return Promise.wrap(this.groups[0]);
                },

                resetGroups: function () {
                    this.groups = [{ startIndex: 0 }];
                    delete this.pinnedItem;
                    delete this.pinnedOffset;
                    this.dirty = true;
                },

                renderGroup: function () {
                    return Promise.wrap(null);
                },

                ensureFirstGroup: function () {
                    return Promise.wrap(this.groups[0]);
                },

                groupOf: function () {
                    return Promise.wrap(this.groups[0]);
                },

                removeElements: function () {
                }
            });
        })
    });

});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/ListView/_Helpers',[
    'exports',
    '../../Core/_Base',
    '../../Animations'
    ], function helpersInit(exports, _Base, Animations) {
    "use strict";

    function nodeListToArray(nodeList) {
        return Array.prototype.slice.call(nodeList);
    }

    function repeat(markup, count) {
        return new Array(count + 1).join(markup);
    }

    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {
        _nodeListToArray: nodeListToArray,
        _repeat: repeat,
        _ListViewAnimationHelper: {
            fadeInElement: function (element) {
                return Animations.fadeIn(element);
            },
            fadeOutElement: function (element) {
                return Animations.fadeOut(element);
            },
            animateEntrance: function (canvas, firstEntrance) {
                return Animations.enterContent(canvas, [{ left: firstEntrance ? "100px" : "40px", top: "0px", rtlflip: true }], { mechanism: "transition" });
            },
        }
    });
});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/ListView/_ItemsContainer',[
    'exports',
    '../../Core/_Base',
    '../../Promise',
    '../../Utilities/_ElementUtilities',
    '../ItemContainer/_Constants'
    ], function itemsContainerInit(exports, _Base, Promise, _ElementUtilities, _Constants) {
    "use strict";

    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {
        _ItemsContainer: _Base.Namespace._lazy(function () {

            var _ItemsContainer = function (site) {
                this.site = site;
                this._itemData = {};
                this.waitingItemRequests = {};
            };
            _ItemsContainer.prototype = {
                requestItem: function ItemsContainer_requestItem(itemIndex) {
                    if (!this.waitingItemRequests[itemIndex]) {
                        this.waitingItemRequests[itemIndex] = [];
                    }

                    var that = this;
                    var promise = new Promise(function (complete) {
                        var itemData = that._itemData[itemIndex];
                        if (itemData && !itemData.detached && itemData.element) {
                            complete(itemData.element);
                        } else {
                            that.waitingItemRequests[itemIndex].push(complete);
                        }
                    });

                    return promise;
                },

                removeItem: function (index) {
                    delete this._itemData[index];
                },

                removeItems: function ItemsContainer_removeItems() {
                    this._itemData = {};
                    this.waitingItemRequests = {};
                },

                setItemAt: function ItemsContainer_setItemAt(itemIndex, itemData) {
                    this._itemData[itemIndex] = itemData;
                    if (!itemData.detached) {
                        this.notify(itemIndex, itemData);
                    }
                },

                notify: function ItemsContainer_notify(itemIndex, itemData) {
                    if (this.waitingItemRequests[itemIndex]) {
                        var requests = this.waitingItemRequests[itemIndex];
                        for (var i = 0; i < requests.length; i++) {
                            requests[i](itemData.element);
                        }

                        this.waitingItemRequests[itemIndex] = [];
                    }
                },

                elementAvailable: function ItemsContainer_elementAvailable(itemIndex) {
                    var itemData = this._itemData[itemIndex];
                    itemData.detached = false;
                    this.notify(itemIndex, itemData);
                },

                itemAt: function ItemsContainer_itemAt(itemIndex) {
                    var itemData = this._itemData[itemIndex];
                    return itemData ? itemData.element : null;
                },

                itemDataAt: function ItemsContainer_itemDataAt(itemIndex) {
                    return this._itemData[itemIndex];
                },

                containerAt: function ItemsContainer_containerAt(itemIndex) {
                    var itemData = this._itemData[itemIndex];
                    return itemData ? itemData.container : null;
                },

                itemBoxAt: function ItemsContainer_itemBoxAt(itemIndex) {
                    var itemData = this._itemData[itemIndex];
                    return itemData ? itemData.itemBox : null;
                },

                itemBoxFrom: function ItemsContainer_containerFrom(element) {
                    while (element && !_ElementUtilities.hasClass(element, _Constants._itemBoxClass)) {
                        element = element.parentNode;
                    }

                    return element;
                },

                containerFrom: function ItemsContainer_containerFrom(element) {
                    while (element && !_ElementUtilities.hasClass(element, _Constants._containerClass)) {
                        element = element.parentNode;
                    }

                    return element;
                },

                index: function ItemsContainer_index(element) {
                    var item = this.containerFrom(element);
                    if (item) {
                        for (var index in this._itemData) {
                            if (this._itemData[index].container === item) {
                                return parseInt(index, 10);
                            }
                        }
                    }

                    return _Constants._INVALID_INDEX;
                },

                each: function ItemsContainer_each(callback) {
                    for (var index in this._itemData) {
                        if (this._itemData.hasOwnProperty(index)) {
                            var itemData = this._itemData[index];
                            callback(parseInt(index, 10), itemData.element, itemData);
                        }
                    }
                },

                eachIndex: function ItemsContainer_each(callback) {
                    for (var index in this._itemData) {
                        if (callback(parseInt(index, 10))) {
                            break;
                        }
                    }
                },

                count: function ItemsContainer_count() {
                    return Object.keys(this._itemData).length;
                }
            };
            return _ItemsContainer;
        })
    });

});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/ListView/_Layouts',[
    'exports',
    '../../Core/_Global',
    '../../Core/_Base',
    '../../Core/_BaseUtils',
    '../../Core/_ErrorFromName',
    '../../Core/_Resources',
    '../../Core/_WriteProfilerMark',
    '../../Animations/_TransitionAnimation',
    '../../Promise',
    '../../Scheduler',
    '../../_Signal',
    '../../Utilities/_Dispose',
    '../../Utilities/_ElementUtilities',
    '../../Utilities/_SafeHtml',
    '../../Utilities/_UI',
    '../ItemContainer/_Constants',
    './_ErrorMessages'
    ], function layouts2Init(exports, _Global, _Base, _BaseUtils, _ErrorFromName, _Resources, _WriteProfilerMark, _TransitionAnimation, Promise, Scheduler, _Signal, _Dispose, _ElementUtilities, _SafeHtml, _UI, _Constants, _ErrorMessages) {
    "use strict";

    var Key = _ElementUtilities.Key,
        uniqueID = _ElementUtilities._uniqueID;

    var strings = {
        get itemInfoIsInvalid() { return "Invalid argument: An itemInfo function must be provided which returns an object with numeric width and height properties."; },
        get groupInfoResultIsInvalid() { return "Invalid result: groupInfo result for cell spanning groups must include the following numeric properties: cellWidth and cellHeight."; }
    };

    //
    // Helpers for dynamic CSS rules
    //
    // Rule deletions are delayed until the next rule insertion. This helps the
    // scenario where a ListView changes layouts. By doing the rule manipulations
    // in a single synchronous block, IE will do 1 layout pass instead of 2.
    //

    // Dynamic CSS rules will be added to this style element
    var layoutStyleElem = _Global.document.createElement("style");
    _Global.document.head.appendChild(layoutStyleElem);

    var nextCssClassId = 0,
        staleClassNames = [];

    // The prefix for the class name should not contain dashes
    function uniqueCssClassName(prefix) {
        return "_win-dynamic-" + prefix + "-" + (nextCssClassId++);
    }

    var browserStyleEquivalents = _BaseUtils._browserStyleEquivalents;
    var transformNames = browserStyleEquivalents["transform"];
    var transitionScriptName = _BaseUtils._browserStyleEquivalents["transition"].scriptName;
    var dragBetweenTransition = transformNames.cssName + " cubic-bezier(0.1, 0.9, 0.2, 1) 167ms";
    var dragBetweenDistance = 12;

    // Removes the dynamic CSS rules corresponding to the classes in staleClassNames
    // from the DOM.
    function flushDynamicCssRules() {
        var rules = layoutStyleElem.sheet.cssRules,
            classCount = staleClassNames.length,
            i,
            j,
            ruleSuffix;

        for (i = 0; i < classCount; i++) {
            ruleSuffix = "." + staleClassNames[i] + " ";
            for (j = rules.length - 1; j >= 0; j--) {
                if (rules[j].selectorText.indexOf(ruleSuffix) !== -1) {
                    layoutStyleElem.sheet.deleteRule(j);
                }
            }
        }
        staleClassNames = [];
    }

    // Creates a dynamic CSS rule and adds it to the DOM. uniqueToken is a class name
    // which uniquely identifies a set of related rules. These rules may be removed
    // using deleteDynamicCssRule. uniqueToken should be created using uniqueCssClassName.
    function addDynamicCssRule(uniqueToken, site, selector, body) {
        flushDynamicCssRules();
        var rule = "." + _Constants._listViewClass + " ." + uniqueToken + " " + selector + " { " +
             body +
        "}";
        var perfId = "_addDynamicCssRule:" + uniqueToken + ",info";
        if (site) {
            site._writeProfilerMark(perfId);
        } else {
            _WriteProfilerMark("WinJS.UI.ListView:Layout" + perfId);
        }
        layoutStyleElem.sheet.insertRule(rule, 0);
    }

    // Marks the CSS rules corresponding to uniqueToken for deletion. The rules
    // should have been added by addDynamicCssRule.
    function deleteDynamicCssRule(uniqueToken) {
        staleClassNames.push(uniqueToken);
    }

    //
    // Helpers shared by all layouts
    //

    // Clamps x to the range first <= x <= last
    function clampToRange(first, last, x) {
        return Math.max(first, Math.min(last, x));
    }

    function getDimension(element, property) {
        return _ElementUtilities.convertToPixels(element, _Global.getComputedStyle(element, null)[property]);
    }

    // Returns the sum of the margin, border, and padding for the side of the
    // element specified by side. side can be "Left", "Right", "Top", or "Bottom".
    function getOuter(side, element) {
        return getDimension(element, "margin" + side) +
            getDimension(element, "border" + side + "Width") +
            getDimension(element, "padding" + side);
    }

    // Returns the total height of element excluding its content height
    function getOuterHeight(element) {
        return getOuter("Top", element) + getOuter("Bottom", element);
    }

    // Returns the total width of element excluding its content width
    function getOuterWidth(element) {
        return getOuter("Left", element) + getOuter("Right", element);
    }

    function forEachContainer(itemsContainer, callback) {
        if (itemsContainer.items) {
            for (var i = 0, len = itemsContainer.items.length; i < len; i++) {
                callback(itemsContainer.items[i], i);
            }
        } else {
            for (var b = 0, index = 0; b < itemsContainer.itemsBlocks.length; b++) {
                var block = itemsContainer.itemsBlocks[b];
                for (var i = 0, len = block.items.length; i < len; i++) {
                    callback(block.items[i], index++);
                }
            }
        }
    }

    function containerFromIndex(itemsContainer, index) {
        if (index < 0) {
            return null;
        }
        if (itemsContainer.items) {
            return (index < itemsContainer.items.length ? itemsContainer.items[index] : null);
        } else {
            var blockSize = itemsContainer.itemsBlocks[0].items.length,
                blockIndex = Math.floor(index / blockSize),
                offset = index % blockSize;
            return (blockIndex < itemsContainer.itemsBlocks.length && offset < itemsContainer.itemsBlocks[blockIndex].items.length ? itemsContainer.itemsBlocks[blockIndex].items[offset] : null);
        }
    }

    function getItemsContainerTree(itemsContainer, tree) {
        var itemsContainerTree;
        for (var i = 0, treeLength = tree.length; i < treeLength; i++) {
            if (tree[i].itemsContainer.element === itemsContainer) {
                itemsContainerTree = tree[i].itemsContainer;
                break;
            }
        }
        return itemsContainerTree;
    }

    function getItemsContainerLength(itemsContainer) {
        var blocksCount,
            itemsCount;
        if (itemsContainer.itemsBlocks) {
            blocksCount = itemsContainer.itemsBlocks.length;
            if (blocksCount > 0) {
                itemsCount = (itemsContainer.itemsBlocks[0].items.length * (blocksCount - 1)) + itemsContainer.itemsBlocks[blocksCount - 1].items.length;
            } else {
                itemsCount = 0;
            }
        } else {
            itemsCount = itemsContainer.items.length;
        }
        return itemsCount;
    }

    var environmentDetails = null;
    // getEnvironmentSupportInformation does one-time checks on several browser-specific environment details (both to check the existence of styles,
    // and also to see if some environments have layout bugs the ListView needs to work around).
    function getEnvironmentSupportInformation(site) {
        if (!environmentDetails) {
            var surface = _Global.document.createElement("div");
            surface.style.width = "500px";
            surface.style.visibility = "hidden";

            // Set up the DOM
            var flexRoot = _Global.document.createElement("div");
            flexRoot.style.cssText += "width: 500px; height: 200px; display: -webkit-flex; display: flex";
            _SafeHtml.setInnerHTMLUnsafe(flexRoot,
                "<div style='height: 100%; display: -webkit-flex; display: flex; flex-flow: column wrap; align-content: flex-start; -webkit-flex-flow: column wrap; -webkit-align-content: flex-start'>" +
                    "<div style='width: 100px; height: 100px'></div>" +
                    "<div style='width: 100px; height: 100px'></div>" +
                    "<div style='width: 100px; height: 100px'></div>" +
                "</div>");
            surface.appendChild(flexRoot);

            // Read from the DOM and detect the bugs
            site.viewport.insertBefore(surface, site.viewport.firstChild);
            var canMeasure = surface.offsetWidth > 0,
                expectedWidth = 200;
            if (canMeasure) {
                // If we can't measure now (e.g. ListView is display:none), leave environmentDetails as null
                // so that we do the detection later when the app calls recalculateItemPosition/forceLayout.

                environmentDetails = {
                    supportsCSSGrid: !!("-ms-grid-row" in _Global.document.documentElement.style),
                    // Detects Chrome flex issue 345433: Incorrect sizing for nested flexboxes
                    // https://code.google.com/p/chromium/issues/detail?id=345433
                    // With nested flexboxes, the inner flexbox's width is proportional to the number of elements intead
                    // of the number of columns.
                    nestedFlexTooLarge: flexRoot.firstElementChild.offsetWidth > expectedWidth,

                    // Detects Firefox issue 995020
                    // https://bugzilla.mozilla.org/show_bug.cgi?id=995020
                    // The three squares we're adding to the nested flexbox should increase the size of the nestedFlex to be 200 pixels wide. This is the case in IE but
                    // currently not in Firefox. In Firefox, the third square will move to the next column, but the container's width won't update for it.
                    nestedFlexTooSmall: flexRoot.firstElementChild.offsetWidth < expectedWidth
                };
            }

            // Clean up the DOM
            site.viewport.removeChild(surface);
        }

        return environmentDetails;
    }

    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {
        Layout: _Base.Class.define(function Layout_ctor() {
            /// <signature helpKeyword="exports.Layout.Layout">
            /// <summary locid="exports.Layout.constructor">
            /// Creates a new Layout object.
            /// </summary>
            /// <param name="options" type="Object" locid="exports.Layout.constructor_p:options">
            /// The set of options to be applied initially to the new Layout object.
            /// </param>
            /// <returns type="exports.Layout" locid="exports.Layout.constructor_returnValue">
            /// The new Layout object.
            /// </returns>
            /// </signature>
        }),

        _LayoutCommon: _Base.Namespace._lazy(function () {
            return _Base.Class.derive(exports.Layout, null, {
                /// <field type="String" oamOptionsDatatype="WinJS.UI.HeaderPosition" locid="WinJS.UI._LayoutCommon.groupHeaderPosition" helpKeyword="WinJS.UI._LayoutCommon.groupHeaderPosition">
                /// Gets or sets the position of group headers relative to their items.
                /// The default value is "top".
                /// </field>
                groupHeaderPosition: {
                    enumerable: true,
                    get: function () {
                        return this._groupHeaderPosition;
                    },
                    set: function (position) {
                        this._groupHeaderPosition = position;
                        this._invalidateLayout();
                    }
                },

                // Implementation of part of ILayout interface

                initialize: function _LayoutCommon_initialize(site, groupsEnabled) {
                    site._writeProfilerMark("Layout:initialize,info");
                    if (!this._inListMode) {
                        _ElementUtilities.addClass(site.surface, _Constants._gridLayoutClass);
                    }

                    if (this._backdropColorClassName) {
                        _ElementUtilities.addClass(site.surface, this._backdropColorClassName);
                    }
                    if (this._disableBackdropClassName) {
                        _ElementUtilities.addClass(site.surface, this._disableBackdropClassName);
                    }
                    this._groups = [];
                    this._groupMap = {};
                    this._oldGroupHeaderPosition = null;
                    this._usingStructuralNodes = false;

                    this._site = site;
                    this._groupsEnabled = groupsEnabled;
                    this._resetAnimationCaches(true);
                },

                /// <field type="String" oamOptionsDatatype="WinJS.UI.Orientation" locid="WinJS.UI._LayoutCommon.orientation" helpKeyword="WinJS.UI._LayoutCommon.orientation">
                /// Gets or sets the orientation for the layout.
                /// The default value is "horizontal".
                /// </field>
                orientation: {
                    enumerable: true,
                    get: function () {
                        return this._orientation;
                    },
                    set: function (orientation) {
                        this._orientation = orientation;
                        this._horizontal = (orientation === "horizontal");
                        this._invalidateLayout();
                    }
                },

                uninitialize: function _LayoutCommon_uninitialize() {
                    var perfId = "Layout:uninitialize,info";
                    function cleanGroups(groups) {
                        var len = groups.length,
                            i;
                        for (i = 0; i < len; i++) {
                            groups[i].cleanUp(true);
                        }
                    }

                    this._elementsToMeasure = {};

                    if (this._site) {
                        this._site._writeProfilerMark(perfId);
                        _ElementUtilities.removeClass(this._site.surface, _Constants._gridLayoutClass);
                        _ElementUtilities.removeClass(this._site.surface, _Constants._headerPositionTopClass);
                        _ElementUtilities.removeClass(this._site.surface, _Constants._headerPositionLeftClass);
                        _ElementUtilities.removeClass(this._site.surface, _Constants._structuralNodesClass);
                        this._site.surface.style.cssText = "";
                        if (this._groups) {
                            cleanGroups(this._groups);
                            this._groups = null;
                            this._groupMap = null;
                        }
                        if (this._layoutPromise) {
                            this._layoutPromise.cancel();
                            this._layoutPromise = null;
                        }
                        this._resetMeasurements();
                        this._oldGroupHeaderPosition = null;
                        this._usingStructuralNodes = null;
                        // The properties given to us by the app (_groupInfo, _itemInfo,
                        // _groupHeaderPosition) are not cleaned up so that the values are
                        // remembered if the layout is reused.

                        if (this._backdropColorClassName) {
                            _ElementUtilities.removeClass(this._site.surface, this._backdropColorClassName);
                            deleteDynamicCssRule(this._backdropColorClassName);
                            this._backdropColorClassName = null;
                        }
                        if (this._disableBackdropClassName) {
                            _ElementUtilities.removeClass(this._site.surface, this._disableBackdropClassName);
                            deleteDynamicCssRule(this._disableBackdropClassName);
                            this._disableBackdropClassName = null;
                        }

                        this._site = null;
                        this._groupsEnabled = null;
                        if (this._animationsRunning) {
                            this._animationsRunning.cancel();
                        }
                        this._animatingItemsBlocks = {};
                    } else {
                        _WriteProfilerMark("WinJS.UI.ListView:" + perfId);
                    }
                },

                numberOfItemsPerItemsBlock: {
                    get: function _LayoutCommon_getNumberOfItemsPerItemsBlock() {
                        function allGroupsAreUniform() {
                            var groupCount = that._site.groupCount,
                                i;

                            for (i = 0; i < groupCount; i++) {
                                if (that._isCellSpanning(i)) {
                                    return false;
                                }
                            }

                            return true;
                        }

                        var that = this;
                        return that._measureItem(0).then(function () {
                            if (that._sizes.viewportContentSize !== that._getViewportCrossSize()) {
                                that._viewportSizeChanged(that._getViewportCrossSize());
                            }

                            if (!that._envInfo.nestedFlexTooLarge && // Disabling structural nodes works around this issue
                                    !that._envInfo.nestedFlexTooSmall &&
                                    allGroupsAreUniform()) {
                                that._usingStructuralNodes = exports._LayoutCommon._barsPerItemsBlock > 0;
                                return exports._LayoutCommon._barsPerItemsBlock * that._itemsPerBar;
                            } else {
                                that._usingStructuralNodes = false;
                                return null;
                            }
                        });
                    }
                },

                layout: function _LayoutCommon_layout(tree, changedRange, modifiedItems, modifiedGroups) {
                    // changedRange implies that the minimum amount of work the layout needs to do is as follows:
                    // - It needs to lay out group shells (header containers and items containers) from
                    //   firstChangedGroup thru lastGroup.
                    // - It needs to ask firstChangedGroup thru lastChangedGroup to lay out their
                    //   contents (i.e. win-containers).
                    // - For each group included in the changedRange, it needs to lay out its
                    //   contents (i.e. win-containers) from firstChangedItem thru lastItem.

                    var that = this;
                    var site = that._site,
                        layoutPerfId = "Layout.layout",
                        realizedRangePerfId = layoutPerfId + ":realizedRange",
                        realizedRangePromise;

                    that._site._writeProfilerMark(layoutPerfId + ",StartTM");
                    that._site._writeProfilerMark(realizedRangePerfId + ",StartTM");

                    // Receives an items container's tree and returns a normalized copy.
                    // This allows us to hold on to a snapshot of the tree without
                    // worrying that items may have been unexpectedly inserted/
                    // removed/moved. The returned tree always appears as though
                    // structural nodes are disabled.
                    function copyItemsContainerTree(itemsContainer) {
                        function copyItems(itemsContainer) {
                            if (that._usingStructuralNodes) {
                                var items = [];
                                itemsContainer.itemsBlocks.forEach(function (itemsBlock) {
                                    items = items.concat(itemsBlock.items.slice(0));
                                });
                                return items;
                            } else {
                                return itemsContainer.items.slice(0);
                            }
                        }

                        return {
                            element: itemsContainer.element,
                            items: copyItems(itemsContainer)
                        };
                    }

                    // Updates the GridLayout's internal state to reflect the current tree.
                    // Similarly tells each group to update its internal state via prepareLayout.
                    // After this function runs, the ILayout functions will return results that
                    // are appropriate for the current tree.
                    function updateGroups() {
                        function createGroup(groupInfo, itemsContainer) {
                            var GroupType = (groupInfo.enableCellSpanning ?
                                Groups.CellSpanningGroup :
                                Groups.UniformGroup);
                            return new GroupType(that, itemsContainer);
                        }

                        var oldRealizedItemRange = (that._groups.length > 0 ?
                                that._getRealizationRange() :
                                null),
                            newGroups = [],
                            prepared = [],
                            cleanUpDom = {},
                            newGroupMap = {},
                            currentIndex = 0,
                            len = tree.length,
                            i;

                        for (i = 0; i < len; i++) {
                            var oldChangedRealizedRangeInGroup = null,
                                groupInfo = that._getGroupInfo(i),
                                groupKey = that._site.groupFromIndex(i).key,
                                oldGroup = that._groupMap[groupKey],
                                wasCellSpanning = oldGroup instanceof Groups.CellSpanningGroup,
                                isCellSpanning = groupInfo.enableCellSpanning;

                            if (oldGroup) {
                                if (wasCellSpanning !== isCellSpanning) {
                                    // The group has changed types so DOM needs to be cleaned up
                                    cleanUpDom[groupKey] = true;
                                } else {
                                    // Compute the range of changed items that is within the group's realized range
                                    var firstChangedIndexInGroup = Math.max(0, changedRange.firstIndex - oldGroup.startIndex),
                                        oldRealizedItemRangeInGroup = that._rangeForGroup(oldGroup, oldRealizedItemRange);
                                    if (oldRealizedItemRangeInGroup && firstChangedIndexInGroup <= oldRealizedItemRangeInGroup.lastIndex) {
                                        // The old changed realized range is non-empty
                                        oldChangedRealizedRangeInGroup = {
                                            firstIndex: Math.max(firstChangedIndexInGroup, oldRealizedItemRangeInGroup.firstIndex),
                                            lastIndex: oldRealizedItemRangeInGroup.lastIndex
                                        };
                                    }
                                }
                            }
                            var group = createGroup(groupInfo, tree[i].itemsContainer.element);
                            var prepareLayoutPromise;
                            if (group.prepareLayoutWithCopyOfTree) {
                                prepareLayoutPromise = group.prepareLayoutWithCopyOfTree(copyItemsContainerTree(tree[i].itemsContainer), oldChangedRealizedRangeInGroup, oldGroup, {
                                    groupInfo: groupInfo,
                                    startIndex: currentIndex,
                                });
                            } else {
                                prepareLayoutPromise = group.prepareLayout(getItemsContainerLength(tree[i].itemsContainer), oldChangedRealizedRangeInGroup, oldGroup, {
                                    groupInfo: groupInfo,
                                    startIndex: currentIndex,
                                });
                            }
                            prepared.push(prepareLayoutPromise);

                            currentIndex += group.count;

                            newGroups.push(group);
                            newGroupMap[groupKey] = group;
                        }

                        return Promise.join(prepared).then(function () {
                            var currentOffset = 0;
                            for (var i = 0, len = newGroups.length; i < len; i++) {
                                var group = newGroups[i];
                                group.offset = currentOffset;
                                currentOffset += that._getGroupSize(group);
                            }

                            // Clean up deleted groups
                            Object.keys(that._groupMap).forEach(function (deletedKey) {
                                var skipDomCleanUp = !cleanUpDom[deletedKey];
                                that._groupMap[deletedKey].cleanUp(skipDomCleanUp);
                            });

                            that._groups = newGroups;
                            that._groupMap = newGroupMap;
                        });
                    }

                    // When doRealizedRange is true, this function is synchronous and has no return value.
                    // When doRealizedRange is false, this function is asynchronous and returns a promise.
                    function layoutGroupContent(groupIndex, realizedItemRange, doRealizedRange) {
                        var group = that._groups[groupIndex],
                            firstChangedIndexInGroup = Math.max(0, changedRange.firstIndex - group.startIndex),
                            realizedItemRangeInGroup = that._rangeForGroup(group, realizedItemRange),
                            beforeRealizedRange;

                        if (doRealizedRange) {
                            group.layoutRealizedRange(firstChangedIndexInGroup, realizedItemRangeInGroup);
                        } else {
                            if (!realizedItemRangeInGroup) {
                                beforeRealizedRange = (group.startIndex + group.count - 1 < realizedItemRange.firstIndex);
                            }

                            return group.layoutUnrealizedRange(firstChangedIndexInGroup, realizedItemRangeInGroup, beforeRealizedRange);
                        }
                    }

                    // Synchronously lays out:
                    // - Realized and unrealized group shells (header containers and items containers).
                    //   This is needed so that each realized group will be positioned at the correct offset.
                    // - Realized items.
                    function layoutRealizedRange() {
                        if (that._groups.length === 0) {
                            return;
                        }

                        var realizedItemRange = that._getRealizationRange(),
                            len = tree.length,
                            i,
                            firstChangedGroup = site.groupIndexFromItemIndex(changedRange.firstIndex);

                        for (i = firstChangedGroup; i < len; i++) {
                            layoutGroupContent(i, realizedItemRange, true);
                            that._layoutGroup(i);
                        }
                    }

                    // Asynchronously lays out the unrealized items
                    function layoutUnrealizedRange() {
                        if (that._groups.length === 0) {
                            return Promise.wrap();
                        }

                        var realizedItemRange = that._getRealizationRange(),
                            // Last group before the realized range which contains 1 or more unrealized items
                            lastGroupBefore = site.groupIndexFromItemIndex(realizedItemRange.firstIndex - 1),
                            // First group after the realized range which contains 1 or more unrealized items
                            firstGroupAfter = site.groupIndexFromItemIndex(realizedItemRange.lastIndex + 1),
                            firstChangedGroup = site.groupIndexFromItemIndex(changedRange.firstIndex),
                            layoutPromises = [],
                            groupCount = that._groups.length;

                        var stop = false;
                        var before = lastGroupBefore;
                        var after = Math.max(firstChangedGroup, firstGroupAfter);
                        after = Math.max(before + 1, after);
                        while (!stop) {
                            stop = true;
                            if (before >= firstChangedGroup) {
                                layoutPromises.push(layoutGroupContent(before, realizedItemRange, false));
                                stop = false;
                                before--;
                            }
                            if (after < groupCount) {
                                layoutPromises.push(layoutGroupContent(after, realizedItemRange, false));
                                stop = false;
                                after++;
                            }
                        }

                        return Promise.join(layoutPromises);
                    }

                    realizedRangePromise = that._measureItem(0).then(function () {
                        _ElementUtilities[that._usingStructuralNodes ? "addClass" : "removeClass"](that._site.surface, _Constants._structuralNodesClass);

                        if (that._sizes.viewportContentSize !== that._getViewportCrossSize()) {
                            that._viewportSizeChanged(that._getViewportCrossSize());
                        }

                        // Move deleted elements to their original positions before calling updateGroups can be slow.
                        that._cacheRemovedElements(modifiedItems, that._cachedItemRecords, that._cachedInsertedItemRecords, that._cachedRemovedItems, false);
                        that._cacheRemovedElements(modifiedGroups, that._cachedHeaderRecords, that._cachedInsertedHeaderRecords, that._cachedRemovedHeaders, true);

                        return updateGroups();
                    }).then(function () {
                        that._syncDomWithGroupHeaderPosition(tree);
                        var surfaceLength = 0;
                        if (that._groups.length > 0) {
                            var lastGroup = that._groups[that._groups.length - 1];
                            surfaceLength = lastGroup.offset + that._getGroupSize(lastGroup);
                        }

                        // Explicitly set the surface width/height. This maintains backwards
                        // compatibility with the original layouts by allowing the items
                        // to be shifted through surface margins.
                        if (that._horizontal) {
                            if (that._groupsEnabled && that._groupHeaderPosition === HeaderPosition.left) {
                                site.surface.style.cssText +=
                                    ";height:" + that._sizes.surfaceContentSize +
                                    "px;-ms-grid-columns: (" + that._sizes.headerContainerWidth + "px auto)[" + tree.length + "]";
                            } else {
                                site.surface.style.height = that._sizes.surfaceContentSize + "px";
                            }
                            if (that._envInfo.nestedFlexTooLarge || that._envInfo.nestedFlexTooSmall) {
                                site.surface.style.width = surfaceLength + "px";
                            }
                        } else {
                            if (that._groupsEnabled && that._groupHeaderPosition === HeaderPosition.top) {
                                site.surface.style.cssText +=
                                    ";width:" + that._sizes.surfaceContentSize +
                                    "px;-ms-grid-rows: (" + that._sizes.headerContainerHeight + "px auto)[" + tree.length + "]";
                            } else {
                                site.surface.style.width = that._sizes.surfaceContentSize + "px";
                            }
                            if (that._envInfo.nestedFlexTooLarge || that._envInfo.nestedFlexTooSmall) {
                                site.surface.style.height = surfaceLength + "px";
                            }
                        }

                        layoutRealizedRange();

                        that._layoutAnimations(modifiedItems, modifiedGroups);

                        that._site._writeProfilerMark(realizedRangePerfId + ":complete,info");
                        that._site._writeProfilerMark(realizedRangePerfId + ",StopTM");
                    }, function (error) {
                        that._site._writeProfilerMark(realizedRangePerfId + ":canceled,info");
                        that._site._writeProfilerMark(realizedRangePerfId + ",StopTM");
                        return Promise.wrapError(error);
                    });

                    that._layoutPromise = realizedRangePromise.then(function () {
                        return layoutUnrealizedRange().then(function () {
                            that._site._writeProfilerMark(layoutPerfId + ":complete,info");
                            that._site._writeProfilerMark(layoutPerfId + ",StopTM");
                        }, function (error) {
                            that._site._writeProfilerMark(layoutPerfId + ":canceled,info");
                            that._site._writeProfilerMark(layoutPerfId + ",StopTM");
                            return Promise.wrapError(error);
                        });
                    });

                    return {
                        realizedRangeComplete: realizedRangePromise,
                        layoutComplete: that._layoutPromise
                    };
                },

                itemsFromRange: function _LayoutCommon_itemsFromRange(firstPixel, lastPixel) {
                    if (this._rangeContainsItems(firstPixel, lastPixel)) {
                        return {
                            firstIndex: this._firstItemFromRange(firstPixel),
                            lastIndex: this._lastItemFromRange(lastPixel)
                        };
                    } else {
                        return {
                            firstIndex: 0,
                            lastIndex: -1
                        };
                    }

                },

                getAdjacent: function _LayoutCommon_getAdjacent(currentItem, pressedKey) {
                    var that = this,
                        groupIndex = that._site.groupIndexFromItemIndex(currentItem.index),
                        group = that._groups[groupIndex],
                        adjustedKey = that._adjustedKeyForOrientationAndBars(that._adjustedKeyForRTL(pressedKey), group instanceof Groups.CellSpanningGroup);

                    if (currentItem.type === _UI.ObjectType.groupHeader) {
                        if (pressedKey === Key.pageUp || pressedKey === Key.pageDown) {
                            // We treat page up and page down keys as if an item had focus
                            currentItem = { type: _UI.ObjectType.item, index: this._groups[currentItem.index].startIndex };
                        } else {
                            switch (adjustedKey) {
                                case Key.leftArrow:
                                    return { type: _UI.ObjectType.groupHeader, index: Math.max(0, currentItem.index - 1) };
                                case Key.rightArrow:
                                    return { type: _UI.ObjectType.groupHeader, index: Math.min(that._groups.length - 1, currentItem.index + 1) };
                            }
                            return currentItem;
                        }
                    }

                    function handleArrowKeys() {
                        var currentItemInGroup = {
                            type: currentItem.type,
                            index: currentItem.index - group.startIndex
                        },
                            newItem = group.getAdjacent(currentItemInGroup, adjustedKey);

                        if (newItem === "boundary") {
                            var prevGroup = that._groups[groupIndex - 1],
                                nextGroup = that._groups[groupIndex + 1],
                                lastGroupIndex = that._groups.length - 1;

                            if (adjustedKey === Key.leftArrow) {
                                if (groupIndex === 0) {
                                    // We're at the beginning of the first group so stay put
                                    return currentItem;
                                } else if (prevGroup instanceof Groups.UniformGroup && group instanceof Groups.UniformGroup) {
                                    // Moving between uniform groups so maintain the row/column if possible
                                    var coordinates = that._indexToCoordinate(currentItemInGroup.index);
                                    var currentSlot = (that._horizontal ? coordinates.row : coordinates.column),
                                        indexOfLastBar = Math.floor((prevGroup.count - 1) / that._itemsPerBar),
                                        startOfLastBar = indexOfLastBar * that._itemsPerBar; // first cell of last bar
                                    return {
                                        type: _UI.ObjectType.item,
                                        index: prevGroup.startIndex + Math.min(prevGroup.count - 1, startOfLastBar + currentSlot)
                                    };
                                } else {
                                    // Moving to or from a cell spanning group so go to the last item
                                    return { type: _UI.ObjectType.item, index: group.startIndex - 1 };
                                }
                            } else if (adjustedKey === Key.rightArrow) {
                                if (groupIndex === lastGroupIndex) {
                                    // We're at the end of the last group so stay put
                                    return currentItem;
                                } else if (group instanceof Groups.UniformGroup && nextGroup instanceof Groups.UniformGroup) {
                                    // Moving between uniform groups so maintain the row/column if possible
                                    var coordinates = that._indexToCoordinate(currentItemInGroup.index),
                                        currentSlot = (that._horizontal ? coordinates.row : coordinates.column);
                                    return {
                                        type: _UI.ObjectType.item,
                                        index: nextGroup.startIndex + Math.min(nextGroup.count - 1, currentSlot)
                                    };
                                } else {
                                    // Moving to or from a cell spanning group so go to the first item
                                    return { type: _UI.ObjectType.item, index: nextGroup.startIndex };
                                }
                            } else {
                                return currentItem;
                            }
                        } else {
                            newItem.index += group.startIndex;
                            return newItem;
                        }
                    }

                    switch (that._adjustedKeyForRTL(pressedKey)) {
                        case Key.upArrow:
                        case Key.leftArrow:
                        case Key.downArrow:
                        case Key.rightArrow:
                            return handleArrowKeys();
                        default:
                            return exports._LayoutCommon.prototype._getAdjacentForPageKeys.call(that, currentItem, pressedKey);
                    }
                },

                hitTest: function _LayoutCommon_hitTest(x, y) {
                    var sizes = this._sizes,
                        result;

                    // Make the coordinates relative to grid layout's content box
                    x -= sizes.layoutOriginX;
                    y -= sizes.layoutOriginY;

                    var groupIndex = this._groupFromOffset(this._horizontal ? x : y),
                        group = this._groups[groupIndex];

                    // Make the coordinates relative to the margin box of the group's items container
                    if (this._horizontal) {
                        x -= group.offset;
                    } else {
                        y -= group.offset;
                    }
                    if (this._groupsEnabled) {
                        if (this._groupHeaderPosition === HeaderPosition.left) {
                            x -= sizes.headerContainerWidth;
                        } else {
                            // Headers above
                            y -= sizes.headerContainerHeight;
                        }
                    }

                    result = group.hitTest(x, y);
                    result.index += group.startIndex;
                    result.insertAfterIndex += group.startIndex;
                    return result;
                },

                // Animation cycle:
                //
                // Edits
                //  ---     UpdateTree        Realize
                // |   |      ---               /\/\
                // |   |     |   |             |    |
                // ------------------------------------------------------- Time
                //      |   |     |   |   |   |      |   |
                //       ---      |   |    ---        ---/\/\/\/\/\/\/\/\/
                //     setupAni   |   | layoutAni    endAni  (animations)
                //                 ---/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
                //                layout    (outside realized range)
                //
                //
                // When there is a modification to the DataSource, the first thing that happens is setupAnimations is
                // called with the current tree. This allows us to cache the locations of the existing items.
                //
                // The next 3 steps have to be completely synchronous otherwise users will see intermediate states and
                // items will blink or jump between locations.
                //
                // ListView modifies the DOM tree. A container is added/removed from the group's itemsContainer for each
                // item added/removed to the group. The existing itemBoxes are shuffled between the different containers.
                // The itemBoxes for the removed items will be removed from the containers completely. Since the DOM tree
                // has been modified we have to apply a transform to position the itemBoxes at their original location. We
                // compare the new locations with the cached locations to figure out how far to translate the itemBoxes.
                // Also the removed items need to be placed back in the DOM without affecting layout (by using position
                // absolute) so that they also do not jump or blink.
                //
                // We only tranform and add back removed items for items which were on screen or are now on screen.
                //
                // Now the ListView can realize other items asynchronously. The items to realize are items which have been
                // inserted into the DataSource or items which are in the realization range because a removal has occurred
                // or the user has scroll slightly.
                //
                // During the realization pass the user may scroll. If they scroll to a range outside of the realization
                // range the items will just appear in the correct location without any animations. If they scroll to a
                // location within the old realization range we still have the items and they will animate correctly.
                //
                // During the realization pass another data source edit can occur. A realization pass is unable to run when
                // the tree and layout are out of sync. Otherwise it may try to request item at index X and get item at
                // index X + 1. This means that if another data source edit occurs before endAnimations is called we
                // restart the whole animation cycle. To group the animations between the two edits we do not reset the
                // caches of item box locations. We could add to it if there were items outside of the range however they
                // will only play half of the animation and will probably look just as ugly as not playing the animation at
                // all. This means setupAnimations will just be a no op in this scenario.
                //
                // This also shows that batching data source edits and only changing the data source when in loadingstate
                // "complete" is still a large performance win.
                //
                // Once the realization pass has finished ListView calls executeAnimations. This is where the layout
                // effectively fades out the removed items (and then removes them from the dom), moves the itemBoxes back
                // to translate(0,0), and fades in the inserted itemBoxes. ListView waits for the executeAnimations promise
                // to complete before allowing more data source edits to trigger another animation cycle.
                //
                // If a resize occurs during the animation cycle the animations will be canceled and items will jump to
                // their final positions.

                setupAnimations: function _LayoutCommon_setupAnimations() {
                    // This function is called after a data source change so that we can cache the locations
                    // of the realized items.

                    if (this._groups.length === 0) {
                        // No animations if we haven't measured before
                        this._resetAnimationCaches();
                        return;
                    }

                    if (Object.keys(this._cachedItemRecords).length) {
                        // Ignore the second call.
                        return;
                    }

                    this._site._writeProfilerMark("Animation:setupAnimations,StartTM");

                    var realizationRange = this._getRealizationRange();

                    var tree = this._site.tree;
                    var itemIndex = 0;
                    var horizontal = (this.orientation === "horizontal");
                    for (var i = 0, treeLength = tree.length; i < treeLength; i++) {
                        var groupBundle = tree[i];
                        var groupHasAtleastOneItemRealized = false;
                        var group = this._groups[i];
                        var groupIsCellSpanning = group instanceof Groups.CellSpanningGroup;
                        var groupOffset = (group ? group.offset : 0);

                        forEachContainer(groupBundle.itemsContainer, function (container, j) {
                            // Don't try to cache something outside of the realization range.
                            if (realizationRange.firstIndex <= itemIndex && realizationRange.lastIndex >= itemIndex) {
                                groupHasAtleastOneItemRealized = true;

                                if (!this._cachedItemRecords[itemIndex]) {
                                    var itemPosition = this._getItemPositionForAnimations(itemIndex, i, j);
                                    var row = itemPosition.row;
                                    var column = itemPosition.column;
                                    var left = itemPosition.left;
                                    var top = itemPosition.top;

                                    // Setting both old and new variables now in case layoutAnimations is called multiple times.
                                    this._cachedItemRecords[itemIndex] = {
                                        oldRow: row,
                                        oldColumn: column,
                                        oldLeft: left,
                                        oldTop: top,
                                        width: itemPosition.width,
                                        height: itemPosition.height,
                                        element: container,
                                        inCellSpanningGroup: groupIsCellSpanning
                                    };
                                }
                            }
                            itemIndex++;
                        }.bind(this));

                        if (groupHasAtleastOneItemRealized) {
                            var groupIndex = i;
                            if (!this._cachedHeaderRecords[groupIndex]) {
                                var headerPosition = this._getHeaderPositionForAnimations(groupIndex);
                                this._cachedHeaderRecords[groupIndex] = {
                                    oldLeft: headerPosition.left,
                                    oldTop: headerPosition.top,
                                    width: headerPosition.width,
                                    height: headerPosition.height,
                                    element: groupBundle.header,
                                };
                            }
                            if (!this._cachedGroupRecords[uniqueID(groupBundle.itemsContainer.element)]) {
                                this._cachedGroupRecords[uniqueID(groupBundle.itemsContainer.element)] = {
                                    oldLeft: horizontal ? groupOffset : 0,
                                    left: horizontal ? groupOffset : 0,
                                    oldTop: horizontal ? 0 : groupOffset,
                                    top: horizontal ? 0 : groupOffset,
                                    element: groupBundle.itemsContainer.element,
                                };
                            }
                        }
                    }

                    this._site._writeProfilerMark("Animation:setupAnimations,StopTM");
                },

                _layoutAnimations: function _LayoutCommon_layoutAnimations(modifiedItems, modifiedGroups) {
                    // This function is called after the DOM tree has been modified to match the data source.
                    // In this function we update the cached records and apply transforms to hide the modifications
                    // from the user. We will remove the transforms via animations in execute animation.

                    if (!Object.keys(this._cachedItemRecords).length &&
                        !Object.keys(this._cachedGroupRecords).length &&
                        !Object.keys(this._cachedHeaderRecords).length) {
                        return;
                    }

                    this._site._writeProfilerMark("Animation:layoutAnimation,StartTM");

                    this._updateAnimationCache(modifiedItems, modifiedGroups);

                    var realizationRange = this._getRealizationRange();

                    var tree = this._site.tree;
                    var itemIndex = 0;
                    var horizontal = (this.orientation === "horizontal");
                    for (var i = 0, treeLength = tree.length; i < treeLength; i++) {
                        var groupBundle = tree[i];
                        var group = this._groups[i];
                        var groupIsCellSpanning = group instanceof Groups.CellSpanningGroup;
                        var groupOffset = (group ? group.offset : 0);
                        var groupMovementX = 0;
                        var groupMovementY = 0;

                        var cachedGroupRecord = this._cachedGroupRecords[uniqueID(groupBundle.itemsContainer.element)];
                        if (cachedGroupRecord) {
                            if (horizontal) {
                                groupMovementX = cachedGroupRecord.oldLeft - groupOffset;
                            } else {
                                groupMovementY = cachedGroupRecord.oldTop - groupOffset;
                            }
                        }


                        forEachContainer(groupBundle.itemsContainer, function (container, j) {
                            // Don't try to cache something outside of the realization range.
                            if (realizationRange.firstIndex <= itemIndex && realizationRange.lastIndex >= itemIndex) {
                                var cachedItemRecord = this._cachedItemRecords[itemIndex];
                                if (cachedItemRecord) {
                                    var itemPosition = this._getItemPositionForAnimations(itemIndex, i, j);
                                    var row = itemPosition.row;
                                    var column = itemPosition.column;
                                    var left = itemPosition.left;
                                    var top = itemPosition.top;

                                    cachedItemRecord.inCellSpanningGroup = cachedItemRecord.inCellSpanningGroup || groupIsCellSpanning;

                                    // If the item has moved we need to update the cache and apply a transform to make it
                                    // appear like it has not moved yet.
                                    if (cachedItemRecord.oldRow !== row ||
                                        cachedItemRecord.oldColumn !== column ||
                                        cachedItemRecord.oldTop !== top ||
                                        cachedItemRecord.oldLeft !== left) {

                                        cachedItemRecord.row = row;
                                        cachedItemRecord.column = column;
                                        cachedItemRecord.left = left;
                                        cachedItemRecord.top = top;

                                        var xOffset = cachedItemRecord.oldLeft - cachedItemRecord.left - groupMovementX;
                                        var yOffset = cachedItemRecord.oldTop - cachedItemRecord.top - groupMovementY;
                                        xOffset = (this._site.rtl ? -1 : 1) * xOffset;

                                        cachedItemRecord.xOffset = xOffset;
                                        cachedItemRecord.yOffset = yOffset;
                                        if (xOffset !== 0 || yOffset !== 0) {
                                            var element = cachedItemRecord.element;
                                            cachedItemRecord.needsToResetTransform = true;
                                            element.style[transitionScriptName] = "";
                                            element.style[transformNames.scriptName] = "translate(" + xOffset + "px," + yOffset + "px)";
                                        }

                                        var itemsBlock = container.parentNode;
                                        if (_ElementUtilities.hasClass(itemsBlock, _Constants._itemsBlockClass)) {
                                            this._animatingItemsBlocks[uniqueID(itemsBlock)] = itemsBlock;
                                        }
                                    }

                                } else {
                                    // Treat items that came from outside of the realization range into the realization range
                                    // as a "Move" which means fade it in.
                                    this._cachedInsertedItemRecords[itemIndex] = container;
                                    container.style[transitionScriptName] = "";
                                    container.style.opacity = 0;
                                }
                            }

                            itemIndex++;
                        }.bind(this));

                        var groupIndex = i;
                        var cachedHeader = this._cachedHeaderRecords[groupIndex];
                        if (cachedHeader) {
                            var headerPosition = this._getHeaderPositionForAnimations(groupIndex);
                            // Note: If a group changes width we allow the header to immediately grow/shrink instead of
                            // animating it. However if the header is removed we stick the header to the last known size.
                            cachedHeader.height = headerPosition.height;
                            cachedHeader.width = headerPosition.width;
                            if (cachedHeader.oldLeft !== headerPosition.left ||
                                cachedHeader.oldTop !== headerPosition.top) {

                                cachedHeader.left = headerPosition.left;
                                cachedHeader.top = headerPosition.top;

                                var xOffset = cachedHeader.oldLeft - cachedHeader.left;
                                var yOffset = cachedHeader.oldTop - cachedHeader.top;
                                xOffset = (this._site.rtl ? -1 : 1) * xOffset;
                                if (xOffset !== 0 || yOffset !== 0) {
                                    cachedHeader.needsToResetTransform = true;
                                    var headerContainer = cachedHeader.element;
                                    headerContainer.style[transitionScriptName] = "";
                                    headerContainer.style[transformNames.scriptName] = "translate(" + xOffset + "px," + yOffset + "px)";
                                }
                            }
                        }

                        if (cachedGroupRecord) {
                            if ((horizontal && cachedGroupRecord.left !== groupOffset) ||
                                (!horizontal && cachedGroupRecord.top !== groupOffset)) {
                                var element = cachedGroupRecord.element;
                                if (groupMovementX === 0 && groupMovementY === 0) {
                                    if (cachedGroupRecord.needsToResetTransform) {
                                        cachedGroupRecord.needsToResetTransform = false;
                                        element.style[transformNames.scriptName] = "";
                                    }
                                } else {
                                    var groupOffsetX = (this._site.rtl ? -1 : 1) * groupMovementX,
                                        groupOffsetY = groupMovementY;
                                    cachedGroupRecord.needsToResetTransform = true;
                                    element.style[transitionScriptName] = "";
                                    element.style[transformNames.scriptName] = "translate(" + groupOffsetX + "px, " + groupOffsetY + "px)";
                                }
                            }
                        }
                    }

                    if (this._inListMode || this._itemsPerBar === 1) {
                        var itemsBlockKeys = Object.keys(this._animatingItemsBlocks);
                        for (var b = 0, blockKeys = itemsBlockKeys.length; b < blockKeys; b++) {
                            this._animatingItemsBlocks[itemsBlockKeys[b]].style.overflow = 'visible';
                        }
                    }

                    this._site._writeProfilerMark("Animation:layoutAnimation,StopTM");
                },

                executeAnimations: function _LayoutCommon_executeAnimations() {
                    // This function is called when we should perform an animation to reveal the true location of the items.
                    // We fade out removed items, fade in added items, and move items which need to be shifted. If they moved
                    // across columns we do a reflow animation.

                    var animationSignal = new _Signal();

                    // Only animate the items on screen.
                    this._filterInsertedElements();
                    this._filterMovedElements();
                    this._filterRemovedElements();

                    if (this._insertedElements.length === 0 && this._removedElements.length === 0 && this._itemMoveRecords.length === 0 && this._moveRecords.length === 0) {
                        // Nothing to animate.
                        this._resetAnimationCaches(true);
                        animationSignal.complete();
                        return animationSignal.promise;
                    }
                    this._animationsRunning = animationSignal.promise;

                    var slowAnimations = exports.Layout._debugAnimations || exports.Layout._slowAnimations;
                    var site = this._site;
                    var insertedElements = this._insertedElements;
                    var removedElements = this._removedElements;
                    var itemMoveRecords = this._itemMoveRecords;
                    var moveRecords = this._moveRecords;

                    var removeDelay = 0;
                    var moveDelay = 0;
                    var addDelay = 0;

                    var currentAnimationPromise = null;
                    var pendingTransitionPromises = [];

                    var hasMultisizeMove = false;
                    var hasReflow = false;
                    var minOffset = 0;
                    var maxOffset = 0;
                    var itemContainersToExpand = {};
                    var upOutDist = 0;
                    var downOutDist = 0;
                    var upInDist = 0;
                    var downInDist = 0;
                    var reflowItemRecords = [];
                    var horizontal = (this.orientation === "horizontal");
                    var oldReflowLayoutProperty = horizontal ? "oldColumn" : "oldRow",
                        reflowLayoutProperty = horizontal ? "column" : "row",
                        oldReflowLayoutPosition = horizontal ? "oldTop" : "oldLeft",
                        reflowLayoutPosition = horizontal ? "top" : "left";

                    var animatingItemsBlocks = this._animatingItemsBlocks;

                    for (var i = 0, len = itemMoveRecords.length; i < len; i++) {
                        var cachedItemRecord = itemMoveRecords[i];
                        if (cachedItemRecord.inCellSpanningGroup) {
                            hasMultisizeMove = true;
                            break;
                        }
                    }

                    var that = this;

                    function startAnimations() {
                        removePhase();
                        if (hasMultisizeMove) {
                            cellSpanningFadeOutMove();
                        } else {
                            if (that._itemsPerBar > 1) {
                                var maxDistance = that._itemsPerBar * that._sizes.containerCrossSize + that._getHeaderSizeContentAdjustment() +
                                    that._sizes.containerMargins[horizontal ? "top" : (site.rtl ? "right" : "left")] +
                                    (horizontal ? that._sizes.layoutOriginY : that._sizes.layoutOriginX);
                                for (var i = 0, len = itemMoveRecords.length; i < len; i++) {
                                    var cachedItemRecord = itemMoveRecords[i];
                                    if (cachedItemRecord[oldReflowLayoutProperty] > cachedItemRecord[reflowLayoutProperty]) {
                                        upOutDist = Math.max(upOutDist, cachedItemRecord[oldReflowLayoutPosition] + cachedItemRecord[horizontal ? "height" : "width"]);
                                        upInDist = Math.max(upInDist, maxDistance - cachedItemRecord[reflowLayoutPosition]);
                                        hasReflow = true;
                                        reflowItemRecords.push(cachedItemRecord);
                                    } else if (cachedItemRecord[oldReflowLayoutProperty] < cachedItemRecord[reflowLayoutProperty]) {
                                        downOutDist = Math.max(downOutDist, maxDistance - cachedItemRecord[oldReflowLayoutPosition]);
                                        downInDist = Math.max(downInDist, cachedItemRecord[reflowLayoutPosition] + cachedItemRecord[horizontal ? "height" : "width"]);
                                        reflowItemRecords.push(cachedItemRecord);
                                        hasReflow = true;
                                    }
                                }
                            }

                            if (site.rtl && !horizontal) {
                                upOutDist *= -1;
                                upInDist *= -1;
                                downOutDist *= -1;
                                downInDist *= -1;
                            }

                            if (hasReflow) {
                                reflowPhase(that._itemsPerBar);
                            } else {
                                directMovePhase();
                            }
                        }
                    }

                    if (exports.Layout._debugAnimations) {
                        _BaseUtils._requestAnimationFrame(function () {
                            startAnimations();
                        });
                    } else {
                        startAnimations();
                    }

                    function waitForNextPhase(nextPhaseCallback) {
                        currentAnimationPromise = Promise.join(pendingTransitionPromises);
                        currentAnimationPromise.done(function () {
                            pendingTransitionPromises = [];
                            // The success is called even if the animations are canceled due to the WinJS.UI.executeTransition
                            // API. To deal with that we check the animationSignal variable. If it is null the animations were
                            // canceled so we shouldn't continue.
                            if (animationSignal) {
                                if (exports.Layout._debugAnimations) {
                                    _BaseUtils._requestAnimationFrame(function () {
                                        nextPhaseCallback();
                                    });
                                } else {
                                    nextPhaseCallback();
                                }
                            }
                        });
                    }

                    function removePhase() {
                        if (removedElements.length) {
                            site._writeProfilerMark("Animation:setupRemoveAnimation,StartTM");

                            moveDelay += 60;
                            addDelay += 60;

                            var removeDuration = 120;
                            if (slowAnimations) {
                                removeDuration *= 10;
                            }

                            pendingTransitionPromises.push(_TransitionAnimation.executeTransition(removedElements,
                            [{
                                property: "opacity",
                                delay: removeDelay,
                                duration: removeDuration,
                                timing: "linear",
                                to: 0,
                                skipStylesReset: true
                            }]));

                            site._writeProfilerMark("Animation:setupRemoveAnimation,StopTM");
                        }
                    }

                    function cellSpanningFadeOutMove() {
                        site._writeProfilerMark("Animation:cellSpanningFadeOutMove,StartTM");

                        // For multisize items which move we fade out and then fade in (opacity 1->0->1)
                        var moveElements = [];
                        for (var i = 0, len = itemMoveRecords.length; i < len; i++) {
                            var cachedItemRecord = itemMoveRecords[i];
                            var container = cachedItemRecord.element;
                            moveElements.push(container);
                        }
                        // Including groups and headers.
                        for (var i = 0, len = moveRecords.length; i < len; i++) {
                            var cachedItemRecord = moveRecords[i];
                            var container = cachedItemRecord.element;
                            moveElements.push(container);
                        }

                        var fadeOutDuration = 120;
                        if (slowAnimations) {
                            fadeOutDuration *= 10;
                        }

                        pendingTransitionPromises.push(_TransitionAnimation.executeTransition(moveElements,
                        {
                            property: "opacity",
                            delay: removeDelay,
                            duration: fadeOutDuration,
                            timing: "linear",
                            to: 0
                        }));

                        waitForNextPhase(cellSpanningFadeInMove);
                        site._writeProfilerMark("Animation:cellSpanningFadeOutMove,StopTM");
                    }

                    function cellSpanningFadeInMove() {
                        site._writeProfilerMark("Animation:cellSpanningFadeInMove,StartTM");

                        addDelay = 0;

                        var moveElements = [];
                        // Move them to their final location.
                        for (var i = 0, len = itemMoveRecords.length; i < len; i++) {
                            var cachedItemRecord = itemMoveRecords[i];
                            var container = cachedItemRecord.element;
                            container.style[transformNames.scriptName] = "";
                            moveElements.push(container);
                        }
                        // Including groups and headers.
                        for (var i = 0, len = moveRecords.length; i < len; i++) {
                            var cachedItemRecord = moveRecords[i];
                            var container = cachedItemRecord.element;
                            container.style[transformNames.scriptName] = "";
                            moveElements.push(container);
                        }

                        var fadeInDuration = 120;
                        if (slowAnimations) {
                            fadeInDuration *= 10;
                        }

                        // For multisize items which move we fade out and then fade in (opacity 1->0->1)
                        pendingTransitionPromises.push(_TransitionAnimation.executeTransition(moveElements,
                        {
                            property: "opacity",
                            delay: addDelay,
                            duration: fadeInDuration,
                            timing: "linear",
                            to: 1
                        }));

                        site._writeProfilerMark("Animation:cellSpanningFadeInMove,StopTM");

                        addPhase();
                    }

                    function reflowPhase(itemsPerBar) {
                        site._writeProfilerMark("Animation:setupReflowAnimation,StartTM");

                        var itemContainersLastBarIndices = {};
                        for (var i = 0, len = reflowItemRecords.length; i < len; i++) {
                            var reflowItemRecord = reflowItemRecords[i];
                            var xOffset = reflowItemRecord.xOffset;
                            var yOffset = reflowItemRecord.yOffset;
                            if (reflowItemRecord[oldReflowLayoutProperty] > reflowItemRecord[reflowLayoutProperty]) {
                                if (horizontal) {
                                    yOffset -= upOutDist;
                                } else {
                                    xOffset -= upOutDist;
                                }
                            } else if (reflowItemRecord[oldReflowLayoutProperty] < reflowItemRecord[reflowLayoutProperty]) {
                                if (horizontal) {
                                    yOffset += downOutDist;
                                } else {
                                    xOffset += downOutDist;
                                }
                            }

                            var container = reflowItemRecord.element;

                            minOffset = Math.min(minOffset, horizontal ? xOffset : yOffset);
                            maxOffset = Math.max(maxOffset, horizontal ? xOffset : yOffset);
                            var itemsContainer = container.parentNode;
                            if (!_ElementUtilities.hasClass(itemsContainer, "win-itemscontainer")) {
                                itemsContainer = itemsContainer.parentNode;
                            }

                            // The itemscontainer element is always overflow:hidden for two reasons:
                            // 1) Better panning performance
                            // 2) When there is margin betweeen the itemscontainer and the surface elements, items that
                            //    reflow should not be visible while they travel long distances or overlap with headers.
                            // This introduces an issue when updateTree makes the itemscontainer smaller, but we need its size
                            // to remain the same size during the execution of the animation to avoid having some of the animated
                            // items being clipped. This is only an issue when items from the last column (in horizontal mode) or row
                            // (in vertical mode) of the group will reflow. Therefore, we change the padding so that the contents are larger,
                            // and then use margin to reverse the size change. We don't do this expansion when it is unnecessary because the
                            // layout/formatting caused by these style changes has significant cost when the group has thousands of items.
                            var lastBarIndex = itemContainersLastBarIndices[uniqueID(itemsContainer)];
                            if (!lastBarIndex) {
                                var count = getItemsContainerLength(getItemsContainerTree(itemsContainer, site.tree));
                                itemContainersLastBarIndices[uniqueID(itemsContainer)] = lastBarIndex = Math.ceil(count / itemsPerBar) - 1;
                            }
                            if (reflowItemRecords[i][horizontal ? "column" : "row"] === lastBarIndex) {
                                itemContainersToExpand[uniqueID(itemsContainer)] = itemsContainer;
                            }

                            var reflowDuration = 80;
                            if (slowAnimations) {
                                reflowDuration *= 10;
                            }

                            pendingTransitionPromises.push(_TransitionAnimation.executeTransition(container,
                            {
                                property: transformNames.cssName,
                                delay: moveDelay,
                                duration: reflowDuration,
                                timing: "cubic-bezier(0.1, 0.9, 0.2, 1)",
                                to: "translate(" + xOffset + "px," + yOffset + "px)"
                            }));
                        }

                        var itemContainerKeys = Object.keys(itemContainersToExpand);
                        for (var i = 0, len = itemContainerKeys.length; i < len; i++) {
                            var itemContainer = itemContainersToExpand[itemContainerKeys[i]];
                            if (site.rtl && horizontal) {
                                itemContainer.style.paddingLeft = (-1 * minOffset) + 'px';
                                itemContainer.style.marginLeft = minOffset + 'px';
                            } else {
                                itemContainer.style[horizontal ? "paddingRight" : "paddingBottom"] = maxOffset + 'px';
                                itemContainer.style[horizontal ? "marginRight" : "marginBottom"] = '-' + maxOffset + 'px';
                            }
                        }
                        var itemsBlockKeys = Object.keys(animatingItemsBlocks);
                        for (var i = 0, len = itemsBlockKeys.length; i < len; i++) {
                            animatingItemsBlocks[itemsBlockKeys[i]].classList.add(_Constants._clipClass);
                        }

                        waitForNextPhase(afterReflowPhase);

                        site._writeProfilerMark("Animation:setupReflowAnimation,StopTM");
                    }

                    function cleanupItemsContainers() {
                        // Reset the styles used to obtain overflow-y: hidden overflow-x: visible.
                        var itemContainerKeys = Object.keys(itemContainersToExpand);
                        for (var i = 0, len = itemContainerKeys.length; i < len; i++) {
                            var itemContainer = itemContainersToExpand[itemContainerKeys[i]];
                            if (site.rtl && horizontal) {
                                itemContainer.style.paddingLeft = '';
                                itemContainer.style.marginLeft = '';
                            } else {
                                itemContainer.style[horizontal ? "paddingRight" : "paddingBottom"] = '';
                                itemContainer.style[horizontal ? "marginRight" : "marginBottom"] = '';
                            }
                        }
                        itemContainersToExpand = {};

                        var itemsBlockKeys = Object.keys(animatingItemsBlocks);
                        for (var i = 0, len = itemsBlockKeys.length; i < len; i++) {
                            var itemsBlock = animatingItemsBlocks[itemsBlockKeys[i]];
                            itemsBlock.style.overflow = '';
                            itemsBlock.classList.remove(_Constants._clipClass);
                        }
                    }

                    function afterReflowPhase() {
                        site._writeProfilerMark("Animation:prepareReflowedItems,StartTM");

                        // Position the items at the edge ready to slide in.
                        for (var i = 0, len = reflowItemRecords.length; i < len; i++) {
                            var reflowItemRecord = reflowItemRecords[i];
                            var xOffset = 0,
                                yOffset = 0;
                            if (reflowItemRecord[oldReflowLayoutProperty] > reflowItemRecord[reflowLayoutProperty]) {
                                if (horizontal) {
                                    yOffset = upInDist;
                                } else {
                                    xOffset = upInDist;
                                }
                            } else if (reflowItemRecord[oldReflowLayoutProperty] < reflowItemRecord[reflowLayoutProperty]) {
                                if (horizontal) {
                                    yOffset = -1 * downInDist;
                                } else {
                                    xOffset = -1 * downInDist;
                                }
                            }
                            reflowItemRecord.element.style[transitionScriptName] = "";
                            reflowItemRecord.element.style[transformNames.scriptName] = "translate(" + xOffset + "px," + yOffset + "px)";
                        }

                        site._writeProfilerMark("Animation:prepareReflowedItems,StopTM");

                        if (exports.Layout._debugAnimations) {
                            _BaseUtils._requestAnimationFrame(function () {
                                directMovePhase(true);
                            });
                        } else {
                            directMovePhase(true);
                        }
                    }

                    function directMovePhase(fastMode) {
                        // For groups and items which move we transition them from transform: translate(Xpx,Ypx) to translate(0px,0px).
                        var duration = 200;
                        if (fastMode) {
                            duration = 150;
                            moveDelay = 0;
                            addDelay = 0;
                        }

                        if (slowAnimations) {
                            duration *= 10;
                        }

                        if (itemMoveRecords.length > 0 || moveRecords.length > 0) {
                            site._writeProfilerMark("Animation:setupMoveAnimation,StartTM");

                            var moveElements = [];
                            for (var i = 0, len = moveRecords.length; i < len; i++) {
                                var container = moveRecords[i].element;
                                moveElements.push(container);
                            }
                            for (var i = 0, len = itemMoveRecords.length; i < len; i++) {
                                var container = itemMoveRecords[i].element;
                                moveElements.push(container);
                            }
                            pendingTransitionPromises.push(_TransitionAnimation.executeTransition(moveElements,
                            {
                                property: transformNames.cssName,
                                delay: moveDelay,
                                duration: duration,
                                timing: "cubic-bezier(0.1, 0.9, 0.2, 1)",
                                to: ""
                            }));

                            addDelay += 80;

                            site._writeProfilerMark("Animation:setupMoveAnimation,StopTM");
                        }

                        addPhase();
                    }

                    function addPhase() {
                        if (insertedElements.length > 0) {
                            site._writeProfilerMark("Animation:setupInsertAnimation,StartTM");

                            var addDuration = 120;
                            if (slowAnimations) {
                                addDuration *= 10;
                            }

                            pendingTransitionPromises.push(_TransitionAnimation.executeTransition(insertedElements,
                            [{
                                property: "opacity",
                                delay: addDelay,
                                duration: addDuration,
                                timing: "linear",
                                to: 1
                            }]));

                            site._writeProfilerMark("Animation:setupInsertAnimation,StopTM");
                        }

                        waitForNextPhase(completePhase);
                    }
                    function completePhase() {
                        site._writeProfilerMark("Animation:cleanupAnimations,StartTM");

                        cleanupItemsContainers();

                        for (var i = 0, len = removedElements.length; i < len; i++) {
                            var container = removedElements[i];
                            if (container.parentNode) {
                                _Dispose._disposeElement(container);
                                container.parentNode.removeChild(container);
                            }
                        }

                        site._writeProfilerMark("Animation:cleanupAnimations,StopTM");

                        that._animationsRunning = null;
                        animationSignal.complete();
                    }
                    this._resetAnimationCaches(true);

                    // The PVL animation library completes sucessfully even if you cancel an animation.
                    // If the animation promise passed to layout is canceled we should cancel the PVL animations and
                    // set a marker for them to be ignored.
                    animationSignal.promise.then(null, function () {
                        // Since it was canceled make sure we still clean up the styles.
                        cleanupItemsContainers();
                        for (var i = 0, len = moveRecords.length; i < len; i++) {
                            var container = moveRecords[i].element;
                            container.style[transformNames.scriptName] = '';
                            container.style.opacity = 1;
                        }
                        for (var i = 0, len = itemMoveRecords.length; i < len; i++) {
                            var container = itemMoveRecords[i].element;
                            container.style[transformNames.scriptName] = '';
                            container.style.opacity = 1;
                        }
                        for (var i = 0, len = insertedElements.length; i < len; i++) {
                            insertedElements[i].style.opacity = 1;
                        }
                        for (var i = 0, len = removedElements.length; i < len; i++) {
                            var container = removedElements[i];
                            if (container.parentNode) {
                                _Dispose._disposeElement(container);
                                container.parentNode.removeChild(container);
                            }
                        }

                        this._animationsRunning = null;
                        animationSignal = null;
                        currentAnimationPromise && currentAnimationPromise.cancel();

                    }.bind(this));

                    return animationSignal.promise;
                },

                dragOver: function _LayoutCommon_dragOver(x, y, dragInfo) {
                    // The coordinates passed to dragOver should be in ListView's viewport space. 0,0 should be the top left corner of the viewport's padding.
                    var indicesAffected = this.hitTest(x, y),
                        groupAffected = (this._groups ? this._site.groupIndexFromItemIndex(indicesAffected.index) : 0),
                        itemsContainer = this._site.tree[groupAffected].itemsContainer,
                        itemsCount = getItemsContainerLength(itemsContainer),
                        indexOffset = (this._groups ? this._groups[groupAffected].startIndex : 0),
                        visibleRange = this._getVisibleRange();

                    indicesAffected.index -= indexOffset;
                    indicesAffected.insertAfterIndex -= indexOffset;
                    visibleRange.firstIndex = Math.max(visibleRange.firstIndex - indexOffset - 1, 0);
                    visibleRange.lastIndex = Math.min(visibleRange.lastIndex - indexOffset + 1, itemsCount);
                    var indexAfter = Math.max(Math.min(itemsCount - 1, indicesAffected.insertAfterIndex), -1),
                        indexBefore = Math.min(indexAfter + 1, itemsCount);

                    if (dragInfo) {
                        for (var i = indexAfter; i >= visibleRange.firstIndex; i--) {
                            if (!dragInfo._isIncluded(i + indexOffset)) {
                                indexAfter = i;
                                break;
                            } else if (i === visibleRange.firstIndex) {
                                indexAfter = -1;
                            }
                        }

                        for (var i = indexBefore; i < visibleRange.lastIndex; i++) {
                            if (!dragInfo._isIncluded(i + indexOffset)) {
                                indexBefore = i;
                                break;
                            } else if (i === (visibleRange.lastIndex - 1)) {
                                indexBefore = itemsCount;
                            }
                        }
                    }

                    var elementBefore = containerFromIndex(itemsContainer, indexBefore),
                        elementAfter = containerFromIndex(itemsContainer, indexAfter);

                    if (this._animatedDragItems) {
                        for (var i = 0, len = this._animatedDragItems.length; i < len; i++) {
                            var item = this._animatedDragItems[i];
                            if (item) {
                                item.style[transitionScriptName] = this._site.animationsDisabled ? "" : dragBetweenTransition;
                                item.style[transformNames.scriptName] = "";
                            }
                        }
                    }
                    this._animatedDragItems = [];
                    var horizontal = this.orientation === "horizontal",
                        inListMode = this._inListMode || this._itemsPerBar === 1;
                    if (this._groups && this._groups[groupAffected] instanceof Groups.CellSpanningGroup) {
                        inListMode = this._groups[groupAffected]._slotsPerColumn === 1;
                    }
                    var horizontalTransform = 0,
                        verticalTransform = 0;
                    // In general, items should slide in the direction perpendicular to the layout's orientation.
                    // In a horizontal layout, items are laid out top to bottom, left to right. For any two neighboring items in this layout, we want to move the first item up and the second down
                    // to denote that any inserted item would go between those two.
                    // Similarily, vertical layout should have the first item move left and the second move right.
                    // List layout is a special case. A horizontal list layout can only lay things out left to right, so it should slide the two items left and right like a vertical grid.
                    // A vertical list can only lay things out top to bottom, so it should slide items up and down like a horizontal grid.
                    // In other words: Apply horizontal transformations if we're a vertical grid or horizontal list, otherwise use vertical transformations.
                    if ((!horizontal && !inListMode) || (horizontal && inListMode)) {
                        horizontalTransform = this._site.rtl ? -dragBetweenDistance : dragBetweenDistance;
                    } else {
                        verticalTransform = dragBetweenDistance;
                    }
                    if (elementBefore) {
                        elementBefore.style[transitionScriptName] = this._site.animationsDisabled ? "" : dragBetweenTransition;
                        elementBefore.style[transformNames.scriptName] = "translate(" + horizontalTransform + "px, " + verticalTransform + "px)";
                        this._animatedDragItems.push(elementBefore);
                    }
                    if (elementAfter) {
                        elementAfter.style[transitionScriptName] = this._site.animationsDisabled ? "" : dragBetweenTransition;
                        elementAfter.style[transformNames.scriptName] = "translate(" + (-horizontalTransform) + "px, -" + verticalTransform + "px)";
                        this._animatedDragItems.push(elementAfter);
                    }
                },

                dragLeave: function _LayoutCommon_dragLeave() {
                    if (this._animatedDragItems) {
                        for (var i = 0, len = this._animatedDragItems.length; i < len; i++) {
                            this._animatedDragItems[i].style[transitionScriptName] = this._site.animationsDisabled ? "" : dragBetweenTransition;
                            this._animatedDragItems[i].style[transformNames.scriptName] = "";
                        }
                    }
                    this._animatedDragItems = [];
                },

                // Private methods

                _setMaxRowsOrColumns: function _LayoutCommon_setMaxRowsOrColumns(value) {
                    if (value === this._maxRowsOrColumns || this._inListMode) {
                        return;
                    }

                    // If container size is unavailable then we do not need to compute itemsPerBar
                    // as it will be computed along with the container size.
                    if (this._sizes && this._sizes.containerSizeLoaded) {
                        this._itemsPerBar = Math.floor(this._sizes.maxItemsContainerContentSize / this._sizes.containerCrossSize);
                        if (value) {
                            this._itemsPerBar = Math.min(this._itemsPerBar, value);
                        }
                        this._itemsPerBar = Math.max(1, this._itemsPerBar);
                    }
                    this._maxRowsOrColumns = value;

                    this._invalidateLayout();
                },

                _getItemPosition: function _LayoutCommon_getItemPosition(itemIndex) {
                    if (this._groupsEnabled) {
                        var groupIndex = Math.min(this._groups.length - 1, this._site.groupIndexFromItemIndex(itemIndex)),
                            group = this._groups[groupIndex],
                            itemOfGroupIndex = itemIndex - group.startIndex;
                        return this._getItemPositionForAnimations(itemIndex, groupIndex, itemOfGroupIndex);
                    } else {
                        return this._getItemPositionForAnimations(itemIndex, 0, itemIndex);
                    }
                },

                _getRealizationRange: function _LayoutCommon_getRealizationRange() {
                    var realizedRange = this._site.realizedRange;
                    return {
                        firstIndex: this._firstItemFromRange(realizedRange.firstPixel),
                        lastIndex: this._lastItemFromRange(realizedRange.lastPixel)
                    };
                },

                _getVisibleRange: function _LayoutCommon_getVisibleRange() {
                    var visibleRange = this._site.visibleRange;
                    return {
                        firstIndex: this._firstItemFromRange(visibleRange.firstPixel),
                        lastIndex: this._lastItemFromRange(visibleRange.lastPixel)
                    };
                },

                _resetAnimationCaches: function _LayoutCommon_resetAnimationCaches(skipReset) {
                    if (!skipReset) {
                        // Caches with move transforms:
                        this._resetStylesForRecords(this._cachedGroupRecords);
                        this._resetStylesForRecords(this._cachedItemRecords);
                        this._resetStylesForRecords(this._cachedHeaderRecords);

                        // Caches with insert transforms:
                        this._resetStylesForInsertedRecords(this._cachedInsertedItemRecords);
                        this._resetStylesForInsertedRecords(this._cachedInsertedHeaderRecords);

                        // Caches with insert transforms:
                        this._resetStylesForRemovedRecords(this._cachedRemovedItems);
                        this._resetStylesForRemovedRecords(this._cachedRemovedHeaders);

                        var itemsBlockKeys = Object.keys(this._animatingItemsBlocks);
                        for (var i = 0, len = itemsBlockKeys.length; i < len; i++) {
                            var itemsBlock = this._animatingItemsBlocks[itemsBlockKeys[i]];
                            itemsBlock.style.overflow = '';
                            itemsBlock.classList.remove(_Constants._clipClass);
                        }
                    }

                    this._cachedGroupRecords = {};
                    this._cachedItemRecords = {};
                    this._cachedHeaderRecords = {};

                    this._cachedInsertedItemRecords = {};
                    this._cachedInsertedHeaderRecords = {};

                    this._cachedRemovedItems = [];
                    this._cachedRemovedHeaders = [];

                    this._animatingItemsBlocks = {};
                },

                _cacheRemovedElements: function _LayoutCommon_cacheRemovedElements(modifiedElements, cachedRecords, cachedInsertedRecords, removedElements, areHeaders) {
                    var leftStr = "left";
                    if (this._site.rtl) {
                        leftStr = "right";
                    }
                    // Offset between the container's content box and its margin box
                    var outerX, outerY;
                    if (areHeaders) {
                        outerX = this._sizes.headerContainerOuterX;
                        outerY = this._sizes.headerContainerOuterY;
                    } else {
                        outerX = this._sizes.containerMargins[leftStr];
                        outerY = this._sizes.containerMargins.top;
                    }

                    // Cache the removed boxes and place them back in the DOM with position absolute
                    // so that they do not appear like they have moved.
                    for (var i = 0, len = modifiedElements.length; i < len; i++) {
                        var modifiedElementLookup = modifiedElements[i];
                        if (modifiedElementLookup.newIndex === -1) {
                            var container = modifiedElementLookup.element;
                            var cachedItemRecord = cachedRecords[modifiedElementLookup.oldIndex];
                            if (cachedItemRecord) {
                                cachedItemRecord.element = container;
                                // This item can no longer be a moved item.
                                delete cachedRecords[modifiedElementLookup.oldIndex];
                                container.style.position = "absolute";
                                container.style[transitionScriptName] = "";
                                container.style.top = cachedItemRecord.oldTop - outerY + "px";
                                container.style[leftStr] = cachedItemRecord.oldLeft - outerX + "px";
                                container.style.width = cachedItemRecord.width + "px";
                                container.style.height = cachedItemRecord.height + "px";
                                container.style[transformNames.scriptName] = "";
                                this._site.surface.appendChild(container);
                                removedElements.push(cachedItemRecord);
                            }
                            if (cachedInsertedRecords[modifiedElementLookup.oldIndex]) {
                                delete cachedInsertedRecords[modifiedElementLookup.oldIndex];
                            }
                        }
                    }
                },
                _cacheInsertedElements: function _LayoutCommon_cacheInsertedItems(modifiedElements, cachedInsertedRecords, cachedRecords) {
                    var newCachedInsertedRecords = {};

                    for (var i = 0, len = modifiedElements.length; i < len; i++) {
                        var modifiedElementLookup = modifiedElements[i];
                        var wasInserted = cachedInsertedRecords[modifiedElementLookup.oldIndex];
                        if (wasInserted) {
                            delete cachedInsertedRecords[modifiedElementLookup.oldIndex];
                        }

                        if (wasInserted || modifiedElementLookup.oldIndex === -1 || modifiedElementLookup.moved) {
                            var cachedRecord = cachedRecords[modifiedElementLookup.newIndex];
                            if (cachedRecord) {
                                delete cachedRecords[modifiedElementLookup.newIndex];
                            }

                            var modifiedElement = modifiedElementLookup.element;
                            newCachedInsertedRecords[modifiedElementLookup.newIndex] = modifiedElement;
                            modifiedElement.style[transitionScriptName] = "";
                            modifiedElement.style[transformNames.scriptName] = "";
                            modifiedElement.style.opacity = 0;
                        }
                    }

                    var keys = Object.keys(cachedInsertedRecords);
                    for (var i = 0, len = keys.length; i < len; i++) {
                        newCachedInsertedRecords[keys[i]] = cachedInsertedRecords[keys[i]];
                    }

                    return newCachedInsertedRecords;
                },
                _resetStylesForRecords: function _LayoutCommon_resetStylesForRecords(recordsHash) {
                    var recordKeys = Object.keys(recordsHash);
                    for (var i = 0, len = recordKeys.length; i < len; i++) {
                        var record = recordsHash[recordKeys[i]];
                        if (record.needsToResetTransform) {
                            record.element.style[transformNames.scriptName] = "";
                            record.needsToResetTransform = false;
                        }
                    }
                },
                _resetStylesForInsertedRecords: function _LayoutCommon_resetStylesForInsertedRecords(insertedRecords) {
                    var insertedRecordKeys = Object.keys(insertedRecords);
                    for (var i = 0, len = insertedRecordKeys.length; i < len; i++) {
                        var insertedElement = insertedRecords[insertedRecordKeys[i]];
                        insertedElement.style.opacity = 1;
                    }
                },
                _resetStylesForRemovedRecords: function _LayoutCommon_resetStylesForRemovedRecords(removedElements) {
                    for (var i = 0, len = removedElements.length; i < len; i++) {
                        var container = removedElements[i].element;
                        if (container.parentNode) {
                            _Dispose._disposeElement(container);
                            container.parentNode.removeChild(container);
                        }
                    }
                },
                _updateAnimationCache: function _LayoutCommon_updateAnimationCache(modifiedItems, modifiedGroups) {
                    // ItemBoxes can change containers so we have to start them back without transforms
                    // and then update them again. ItemsContainers don't need to do this.
                    this._resetStylesForRecords(this._cachedItemRecords);
                    this._resetStylesForRecords(this._cachedHeaderRecords);
                    // Go through all the inserted records and reset their insert transforms.
                    this._resetStylesForInsertedRecords(this._cachedInsertedItemRecords);
                    this._resetStylesForInsertedRecords(this._cachedInsertedHeaderRecords);

                    var existingContainers = {};
                    var realizationRange = this._getRealizationRange();
                    var tree = this._site.tree;
                    for (var i = 0, itemIndex = 0, treeLength = tree.length; i < treeLength; i++) {
                        forEachContainer(tree[i].itemsContainer, function (container) {
                            if (realizationRange.firstIndex <= itemIndex && realizationRange.lastIndex >= itemIndex) {
                                existingContainers[uniqueID(container)] = true;
                            }
                            itemIndex++;
                        });
                    }

                    // Update the indicies before the insert since insert needs the new containers.
                    function updateIndicies(modifiedElements, cachedRecords) {
                        var updatedCachedRecords = {};

                        for (var i = 0, len = modifiedElements.length; i < len; i++) {
                            var modifiedElementLookup = modifiedElements[i];
                            var cachedRecord = cachedRecords[modifiedElementLookup.oldIndex];
                            if (cachedRecord) {
                                updatedCachedRecords[modifiedElementLookup.newIndex] = cachedRecord;
                                cachedRecord.element = modifiedElementLookup.element;
                                delete cachedRecords[modifiedElementLookup.oldIndex];
                            }
                        }
                        var cachedRecordKeys = Object.keys(cachedRecords);
                        for (var i = 0, len = cachedRecordKeys.length; i < len; i++) {
                            var key = cachedRecordKeys[i],
                                record = cachedRecords[key];
                            // We need to filter out containers which were removed from the DOM. If container's item
                            // wasn't realized container can be removed without adding record to modifiedItems.
                            if (!record.element || existingContainers[uniqueID(record.element)]) {
                                updatedCachedRecords[key] = record;
                            }
                        }
                        return updatedCachedRecords;
                    }

                    this._cachedItemRecords = updateIndicies(modifiedItems, this._cachedItemRecords);
                    this._cachedHeaderRecords = updateIndicies(modifiedGroups, this._cachedHeaderRecords);

                    this._cachedInsertedItemRecords = this._cacheInsertedElements(modifiedItems, this._cachedInsertedItemRecords, this._cachedItemRecords);
                    this._cachedInsertedHeaderRecords = this._cacheInsertedElements(modifiedGroups, this._cachedInsertedHeaderRecords, this._cachedHeaderRecords);
                },
                _filterRemovedElements: function _LayoutCommon_filterRemovedElements() {
                    this._removedElements = [];

                    if (this._site.animationsDisabled) {
                        this._resetStylesForRemovedRecords(this._cachedRemovedItems);
                        this._resetStylesForRemovedRecords(this._cachedRemovedHeaders);
                        return;
                    }

                    var that = this;
                    var oldLeftStr = this.orientation === "horizontal" ? "oldLeft" : "oldTop";
                    var widthStr = this.orientation === "horizontal" ? "width" : "height";

                    var visibleFirstPixel = this._site.scrollbarPos;
                    var visibleLastPixel = visibleFirstPixel + this._site.viewportSize[widthStr] - 1;

                    function filterRemovedElements(removedRecordArray, removedElementsArray) {
                        for (var i = 0, len = removedRecordArray.length; i < len; i++) {
                            var removedItem = removedRecordArray[i];
                            var container = removedItem.element;
                            if (removedItem[oldLeftStr] + removedItem[widthStr] - 1 < visibleFirstPixel || removedItem[oldLeftStr] > visibleLastPixel || !that._site.viewport.contains(container)) {
                                if (container.parentNode) {
                                    _Dispose._disposeElement(container);
                                    container.parentNode.removeChild(container);
                                }
                            } else {
                                removedElementsArray.push(container);
                            }
                        }
                    }

                    filterRemovedElements(this._cachedRemovedItems, this._removedElements);
                    filterRemovedElements(this._cachedRemovedHeaders, this._removedElements);
                },

                _filterInsertedElements: function _LayoutCommon_filterInsertedElements() {
                    this._insertedElements = [];
                    if (this._site.animationsDisabled) {
                        this._resetStylesForInsertedRecords(this._cachedInsertedItemRecords);
                        this._resetStylesForInsertedRecords(this._cachedInsertedHeaderRecords);
                        return;
                    }

                    var that = this;
                    var visibleRange = this._getVisibleRange();

                    function filterInsertedElements(cachedInsertedRecords, insertedElementsArray) {
                        var recordKeys = Object.keys(cachedInsertedRecords);
                        for (var i = 0, len = recordKeys.length; i < len; i++) {
                            var itemIndex = recordKeys[i];
                            var insertedRecord = cachedInsertedRecords[itemIndex];
                            if (itemIndex < visibleRange.firstIndex || itemIndex > visibleRange.lastIndex || that._site.viewport.contains(insertedRecord.element)) {
                                insertedRecord.style.opacity = 1;
                            } else {
                                insertedElementsArray.push(insertedRecord);
                            }
                        }
                    }

                    filterInsertedElements(this._cachedInsertedItemRecords, this._insertedElements);
                    filterInsertedElements(this._cachedInsertedHeaderRecords, this._insertedElements);
                },

                _filterMovedElements: function _LayoutCommon_filterMovedElements() {
                    var that = this;

                    // This filters all the items and groups down which could have moved to just the items on screen.
                    // The items which are not going to animate are immediately shown in their correct final location.
                    var oldLeftStr = this.orientation === "horizontal" ? "oldLeft" : "oldTop";
                    var leftStr = this.orientation === "horizontal" ? "left" : "top";
                    var widthStr = this.orientation === "horizontal" ? "width" : "height";

                    var realizationRange = this._getRealizationRange();
                    var visibleFirstPixel = this._site.scrollbarPos;
                    var visibleLastPixel = visibleFirstPixel + this._site.viewportSize[widthStr] - 1;

                    // ItemMove can reflow across column or fade in/out due to multisize.
                    this._itemMoveRecords = [];
                    this._moveRecords = [];

                    if (!this._site.animationsDisabled) {
                        var tree = this._site.tree;
                        var itemIndex = 0;
                        for (var i = 0, treeLength = tree.length; i < treeLength; i++) {
                            var groupBundle = tree[i];
                            var groupHasItemToAnimate = false;

                            forEachContainer(groupBundle.itemsContainer, function () {
                                if (realizationRange.firstIndex <= itemIndex && realizationRange.lastIndex >= itemIndex) {
                                    var cachedItemRecord = this._cachedItemRecords[itemIndex];
                                    if (cachedItemRecord) {
                                        var shouldAnimate = ((cachedItemRecord[oldLeftStr] + cachedItemRecord[widthStr] - 1 >= visibleFirstPixel && cachedItemRecord[oldLeftStr] <= visibleLastPixel) ||
                                                            (cachedItemRecord[leftStr] + cachedItemRecord[widthStr] - 1 >= visibleFirstPixel && cachedItemRecord[leftStr] <= visibleLastPixel)) &&
                                                            that._site.viewport.contains(cachedItemRecord.element);
                                        if (shouldAnimate) {
                                            groupHasItemToAnimate = true;
                                            if (cachedItemRecord.needsToResetTransform) {
                                                this._itemMoveRecords.push(cachedItemRecord);
                                                delete this._cachedItemRecords[itemIndex];
                                            }
                                        }
                                    }
                                }
                                itemIndex++;
                            }.bind(this));

                            var groupIndex = i;
                            var cachedHeaderRecord = this._cachedHeaderRecords[groupIndex];
                            if (cachedHeaderRecord) {
                                if (groupHasItemToAnimate && cachedHeaderRecord.needsToResetTransform) {
                                    this._moveRecords.push(cachedHeaderRecord);
                                    delete this._cachedHeaderRecords[groupIndex];
                                }
                            }

                            var cachedGroupRecord = this._cachedGroupRecords[uniqueID(groupBundle.itemsContainer.element)];
                            if (cachedGroupRecord) {
                                if (groupHasItemToAnimate && cachedGroupRecord.needsToResetTransform) {
                                    this._moveRecords.push(cachedGroupRecord);
                                    delete this._cachedGroupRecords[uniqueID(groupBundle.itemsContainer.element)];
                                }
                            }
                        }
                    }

                    // Reset transform for groups and items that were never on screen.
                    this._resetStylesForRecords(this._cachedGroupRecords);
                    this._resetStylesForRecords(this._cachedItemRecords);
                    this._resetStylesForRecords(this._cachedHeaderRecords);
                },

                _getItemPositionForAnimations: function _LayoutCommon_getItemPositionForAnimations(itemIndex, groupIndex, itemOfGroupIndex) {
                    // Top/Left are used to know if the item has moved and also used to position the item if removed.
                    // Row/Column are used to know if a reflow animation should occur
                    // Height/Width are used when positioning a removed item without impacting layout.
                    // The returned rectangle refers to the win-container's border/padding/content box. Coordinates
                    // are relative to the viewport.
                    var group = this._groups[groupIndex];
                    var itemPosition = group.getItemPositionForAnimations(itemOfGroupIndex);
                    var groupOffset = (this._groups[groupIndex] ? this._groups[groupIndex].offset : 0);
                    var headerWidth = (this._groupsEnabled && this._groupHeaderPosition === HeaderPosition.left ? this._sizes.headerContainerWidth : 0);
                    var headerHeight = (this._groupsEnabled && this._groupHeaderPosition === HeaderPosition.top ? this._sizes.headerContainerHeight : 0);

                    itemPosition.left += this._sizes.layoutOriginX + headerWidth + this._sizes.itemsContainerOuterX;
                    itemPosition.top += this._sizes.layoutOriginY + headerHeight + this._sizes.itemsContainerOuterY;
                    itemPosition[this._horizontal ? "left" : "top"] += groupOffset;
                    return itemPosition;
                },

                _getHeaderPositionForAnimations: function (groupIndex) {
                    // Top/Left are used to know if the item has moved.
                    // Height/Width are used when positioning a removed item without impacting layout.
                    // The returned rectangle refers to the header container's content box. Coordinates
                    // are relative to the viewport.

                    var headerPosition;

                    if (this._groupsEnabled) {
                        var width = this._sizes.headerContainerWidth - this._sizes.headerContainerOuterWidth,
                            height = this._sizes.headerContainerHeight - this._sizes.headerContainerOuterHeight;
                        if (this._groupHeaderPosition === HeaderPosition.left && !this._horizontal) {
                            height = this._groups[groupIndex].getItemsContainerSize() - this._sizes.headerContainerOuterHeight;
                        } else if (this._groupHeaderPosition === HeaderPosition.top && this._horizontal) {
                            width = this._groups[groupIndex].getItemsContainerSize() - this._sizes.headerContainerOuterWidth;
                        }

                        var offsetX = this._horizontal ? this._groups[groupIndex].offset : 0,
                            offsetY = this._horizontal ? 0 : this._groups[groupIndex].offset;
                        headerPosition = {
                            top: this._sizes.layoutOriginY + offsetY + this._sizes.headerContainerOuterY,
                            left: this._sizes.layoutOriginX + offsetX + this._sizes.headerContainerOuterX,
                            height: height,
                            width: width
                        };
                    } else {
                        headerPosition = {
                            top: 0,
                            left: 0,
                            height: 0,
                            width: 0
                        };
                    }
                    return headerPosition;
                },

                _rangeContainsItems: function _LayoutCommon_rangeContainsItems(firstPixel, lastPixel) {
                    if (this._groups.length === 0) {
                        return false;
                    } else {
                        var lastGroup = this._groups[this._groups.length - 1],
                            lastPixelOfLayout = this._sizes.layoutOrigin + lastGroup.offset + this._getGroupSize(lastGroup) - 1;

                        return lastPixel >= 0 && firstPixel <= lastPixelOfLayout;
                    }
                },

                _itemFromOffset: function _LayoutCommon_itemFromOffset(offset, options) {
                    // supported options are:
                    // - wholeItem: when set to true the fully visible item is returned
                    // - last: if 1 the last item is returned. if 0 the first
                    var that = this;
                    if (this._groups.length === 0) {
                        return 0;
                    }

                    function assignItemMargins(offset) {
                        if (!options.wholeItem) {
                            // This makes it such that if a container's margin is on screen but all of its
                            // content is off screen then we'll treat the container as being off screen.
                            var marginPropLast = (that._horizontal ? (that._site.rtl ? "right" : "left") : "top"),
                                marginPropFirst = (that._horizontal ? (that._site.rtl ? "left" : "right") : "bottom");
                            if (options.last) {
                                // When looking for the *last* item, treat all container margins
                                // as belonging to the container *before* the margin.
                                return offset - that._sizes.containerMargins[marginPropLast];
                            } else {
                                // When looking for the *first* item, treat all container margins
                                // as belonging to the container *after* the margin.
                                return offset + that._sizes.containerMargins[marginPropFirst];
                            }
                        }
                        return offset;
                    }

                    // Assign the headers and margins to the appropriate groups.
                    function assignGroupMarginsAndHeaders(offset) {
                        if (options.last) {
                            // When looking for the *last* group, the *trailing* header and margin belong to the group.
                            return offset - that._getHeaderSizeGroupAdjustment() - that._sizes.itemsContainerOuterStart;
                        } else {
                            // When looking for the *first* group, the *leading* header and margin belong to the group.
                            // No need to make any adjustments to offset because the correct header and margin
                            // already belong to the group.
                            return offset;
                        }
                    }

                    options = options || {};

                    // Make offset relative to layout's content box
                    offset -= this._sizes.layoutOrigin;

                    offset = assignItemMargins(offset);

                    var groupIndex = this._groupFromOffset(assignGroupMarginsAndHeaders(offset)),
                        group = this._groups[groupIndex];

                    // Make offset relative to the margin box of the group's items container
                    offset -= group.offset;
                    offset -= this._getHeaderSizeGroupAdjustment();

                    return group.startIndex + group.itemFromOffset(offset, options);
                },

                _firstItemFromRange: function _LayoutCommon_firstItemFromRange(firstPixel, options) {
                    // supported options are:
                    // - wholeItem: when set to true the first fully visible item is returned
                    options = options || {};
                    options.last = 0;
                    return this._itemFromOffset(firstPixel, options);
                },

                _lastItemFromRange: function _LayoutCommon_lastItemFromRange(lastPixel, options) {
                    // supported options are:
                    // - wholeItem: when set to true the last fully visible item is returned
                    options = options || {};
                    options.last = 1;
                    return this._itemFromOffset(lastPixel, options);
                },

                _adjustedKeyForRTL: function _LayoutCommon_adjustedKeyForRTL(key) {
                    if (this._site.rtl) {
                        if (key === Key.leftArrow) {
                            key = Key.rightArrow;
                        } else if (key === Key.rightArrow) {
                            key = Key.leftArrow;
                        }
                    }
                    return key;
                },

                _adjustedKeyForOrientationAndBars: function _LayoutCommon_adjustedKeyForOrientationAndBars(key, cellSpanningGroup) {
                    var newKey = key;

                    // Don't support cell spanning
                    if (cellSpanningGroup) {
                        return key;
                    }
                    // First, convert the key into a virtual form based off of horizontal layouts.
                    // In a horizontal layout, left/right keys switch between columns (AKA "bars"), and
                    // up/down keys switch between rows (AKA "slots").
                    // In vertical mode, we want up/down to switch between rows (AKA "bars" when vertical),
                    // and left/right to switch between columns (AKA "slots" when vertical).
                    // The first step is to convert keypresses in vertical so that up/down always correspond to moving between slots,
                    // and left/right moving between bars.
                    if (!this._horizontal) {
                        switch (newKey) {
                            case Key.leftArrow:
                                newKey = Key.upArrow;
                                break;
                            case Key.rightArrow:
                                newKey = Key.downArrow;
                                break;
                            case Key.upArrow:
                                newKey = Key.leftArrow;
                                break;
                            case Key.downArrow:
                                newKey = Key.rightArrow;
                                break;
                        }
                    }

                    // Next, if we only have one item per bar, we'll make the change-slots-key the same as the change-bars-key
                    if (this._itemsPerBar === 1) {
                        if (newKey === Key.upArrow) {
                            newKey = Key.leftArrow;
                        } else if (newKey === Key.downArrow) {
                            newKey = Key.rightArrow;
                        }
                    }

                    return newKey;
                },

                _getAdjacentForPageKeys: function _LayoutCommon_getAdjacentForPageKeys(currentItem, pressedKey) {
                    var containerMargins = this._sizes.containerMargins,
                        marginSum = (this.orientation === "horizontal" ?
                            containerMargins.left + containerMargins.right :
                            containerMargins.top + containerMargins.bottom);

                    var viewportLength = this._site.viewportSize[this.orientation === "horizontal" ? "width" : "height"],
                        firstPixel = this._site.scrollbarPos,
                        lastPixel = firstPixel + viewportLength - 1 - containerMargins[(this.orientation === "horizontal" ? "right" : "bottom")],
                        newFocus;

                    // Handles page up by attempting to choose the first fully visible item
                    // on the current page. If that item already has focus, chooses the
                    // first item on the previous page. Page down is handled similarly.

                    var firstIndex = this._firstItemFromRange(firstPixel, { wholeItem: true }),
                        lastIndex = this._lastItemFromRange(lastPixel, { wholeItem: false }),
                        currentItemPosition = this._getItemPosition(currentItem.index);


                    var offscreen = false;
                    if (currentItem.index < firstIndex || currentItem.index > lastIndex) {
                        offscreen = true;
                        if (this.orientation === "horizontal") {
                            firstPixel = currentItemPosition.left - marginSum;
                        } else {
                            firstPixel = currentItemPosition.top - marginSum;
                        }
                        lastPixel = firstPixel + viewportLength - 1;
                        firstIndex = this._firstItemFromRange(firstPixel, { wholeItem: true });
                        lastIndex = this._lastItemFromRange(lastPixel, { wholeItem: false });
                    }

                    if (pressedKey === Key.pageUp) {
                        if (!offscreen && firstIndex !== currentItem.index) {
                            return { type: _UI.ObjectType.item, index: firstIndex };
                        }
                        var end;
                        if (this.orientation === "horizontal") {
                            end = currentItemPosition.left + currentItemPosition.width + marginSum + containerMargins.left;
                        } else {
                            end = currentItemPosition.top + currentItemPosition.height + marginSum + containerMargins.bottom;
                        }
                        var firstIndexOnPrevPage = this._firstItemFromRange(end - viewportLength, { wholeItem: true });
                        if (currentItem.index === firstIndexOnPrevPage) {
                            // The current item is so big that it spanned from the previous page, so we want to at least
                            // move to the previous item.
                            newFocus = Math.max(0, currentItem.index - this._itemsPerBar);
                        } else {
                            newFocus = firstIndexOnPrevPage;
                        }
                    } else {
                        if (!offscreen && lastIndex !== currentItem.index) {
                            return { type: _UI.ObjectType.item, index: lastIndex };
                        }
                        // We need to subtract twice the marginSum from the item's starting position because we need to
                        // consider that ensureVisible will scroll the viewport to include the new items margin as well
                        // which may push the current item just off screen.
                        var start;
                        if (this.orientation === "horizontal") {
                            start = currentItemPosition.left - marginSum - containerMargins.right;
                        } else {
                            start = currentItemPosition.top - marginSum - containerMargins.bottom;
                        }
                        var lastIndexOnNextPage = Math.max(0, this._lastItemFromRange(start + viewportLength - 1, { wholeItem: true }));
                        if (currentItem.index === lastIndexOnNextPage) {
                            // The current item is so big that it spans across the next page, so we want to at least
                            // move to the next item. It is also ok to blindly increment this index w/o bound checking
                            // since the browse mode clamps the bounds for page keys. This way we do not have to
                            // asynchronoulsy request the count here.
                            newFocus = currentItem.index + this._itemsPerBar;
                        } else {
                            newFocus = lastIndexOnNextPage;
                        }
                    }

                    return { type: _UI.ObjectType.item, index: newFocus };
                },

                _isCellSpanning: function _LayoutCommon_isCellSpanning(groupIndex) {
                    var group = this._site.groupFromIndex(groupIndex),
                        groupInfo = this._groupInfo;

                    if (groupInfo) {
                        return !!(typeof groupInfo === "function" ? groupInfo(group) : groupInfo).enableCellSpanning;
                    } else {
                        return false;
                    }
                },

                // Can only be called after measuring has been completed
                _getGroupInfo: function _LayoutCommon_getGroupInfo(groupIndex) {
                    var group = this._site.groupFromIndex(groupIndex),
                        groupInfo = this._groupInfo,
                        margins = this._sizes.containerMargins,
                        adjustedInfo = { enableCellSpanning: false };

                    groupInfo = (typeof groupInfo === "function" ? groupInfo(group) : groupInfo);
                    if (groupInfo) {
                        if (groupInfo.enableCellSpanning && (+groupInfo.cellWidth !== groupInfo.cellWidth || +groupInfo.cellHeight !== groupInfo.cellHeight)) {
                            throw new _ErrorFromName("WinJS.UI.GridLayout.GroupInfoResultIsInvalid", strings.groupInfoResultIsInvalid);
                        }
                        adjustedInfo = {
                            enableCellSpanning: !!groupInfo.enableCellSpanning,
                            cellWidth: groupInfo.cellWidth + margins.left + margins.right,
                            cellHeight: groupInfo.cellHeight + margins.top + margins.bottom
                        };
                    }

                    return adjustedInfo;
                },

                // itemIndex is optional
                _getItemInfo: function _LayoutCommon_getItemInfo(itemIndex) {
                    var result;
                    if (!this._itemInfo || typeof this._itemInfo !== "function") {
                        if (this._useDefaultItemInfo) {
                            result = this._defaultItemInfo(itemIndex);
                        } else {
                            throw new _ErrorFromName("WinJS.UI.GridLayout.ItemInfoIsInvalid", strings.itemInfoIsInvalid);
                        }
                    } else {
                        result = this._itemInfo(itemIndex);
                    }
                    return Promise.as(result).then(function (size) {
                        if (!size || +size.width !== size.width || +size.height !== size.height) {
                            throw new _ErrorFromName("WinJS.UI.GridLayout.ItemInfoIsInvalid", strings.itemInfoIsInvalid);
                        }
                        return size;
                    });
                },

                _defaultItemInfo: function _LayoutCommon_defaultItemInfo(itemIndex) {
                    var that = this;
                    return this._site.renderItem(this._site.itemFromIndex(itemIndex)).then(function (element) {
                        that._elementsToMeasure[itemIndex] = {
                            element: element
                        };
                        return that._measureElements();
                    }).then(
                        function () {
                            var entry = that._elementsToMeasure[itemIndex],
                                size = {
                                    width: entry.width,
                                    height: entry.height
                                };

                            delete that._elementsToMeasure[itemIndex];
                            return size;
                        },
                        function (error) {
                            delete that._elementsToMeasure[itemIndex];
                            return Promise.wrapError(error);
                        }
                    );
                },

                _getGroupSize: function _LayoutCommon_getGroupSize(group) {
                    var headerContainerMinSize = 0;

                    if (this._groupsEnabled) {
                        if (this._horizontal && this._groupHeaderPosition === HeaderPosition.top) {
                            headerContainerMinSize = this._sizes.headerContainerMinWidth;
                        } else if (!this._horizontal && this._groupHeaderPosition === HeaderPosition.left) {
                            headerContainerMinSize = this._sizes.headerContainerMinHeight;
                        }
                    }
                    return Math.max(headerContainerMinSize, group.getItemsContainerSize() + this._getHeaderSizeGroupAdjustment());
                },

                // offset should be relative to the grid layout's content box
                _groupFromOffset: function _LayoutCommon_groupFromOffset(offset) {
                    return offset < this._groups[0].offset ?
                        0 :
                        this._groupFrom(function (group) {
                            return offset < group.offset;
                        });
                },

                _groupFromImpl: function _LayoutCommon_groupFromImpl(fromGroup, toGroup, comp) {
                    if (toGroup < fromGroup) {
                        return null;
                    }

                    var center = fromGroup + Math.floor((toGroup - fromGroup) / 2),
                        centerGroup = this._groups[center];

                    if (comp(centerGroup, center)) {
                        return this._groupFromImpl(fromGroup, center - 1, comp);
                    } else if (center < toGroup && !comp(this._groups[center + 1], center + 1)) {
                        return this._groupFromImpl(center + 1, toGroup, comp);
                    } else {
                        return center;
                    }
                },

                _groupFrom: function _LayoutCommon_groupFrom(comp) {
                    if (this._groups.length > 0) {
                        var lastGroupIndex = this._groups.length - 1,
                            lastGroup = this._groups[lastGroupIndex];

                        if (!comp(lastGroup, lastGroupIndex)) {
                            return lastGroupIndex;
                        } else {
                            return this._groupFromImpl(0, this._groups.length - 1, comp);
                        }
                    } else {
                        return null;
                    }
                },

                _invalidateLayout: function _LayoutCommon_invalidateLayout() {
                    if (this._site) {
                        this._site.invalidateLayout();
                    }
                },

                _resetMeasurements: function _LayoutCommon_resetMeasurements() {
                    if (this._measuringPromise) {
                        this._measuringPromise.cancel();
                        this._measuringPromise = null;
                    }
                    if (this._containerSizeClassName) {
                        _ElementUtilities.removeClass(this._site.surface, this._containerSizeClassName);
                        deleteDynamicCssRule(this._containerSizeClassName);
                        this._containerSizeClassName = null;
                    }
                    this._sizes = null;
                    this._resetAnimationCaches();
                },

                _measureElements: function _LayoutCommon_measureElements() {
                    // batching measurements to minimalize number of layout passes
                    if (!this._measuringElements) {
                        var that = this;
                        // Schedule a job so that:
                        //  1. Calls to _measureElements are batched.
                        //  2. that._measuringElements is set before the promise handler is executed
                        //     (that._measuringElements is used within the handler).
                        that._measuringElements = Scheduler.schedulePromiseHigh(null, "WinJS.UI.GridLayout._measuringElements").then(
                            function measure() {
                                that._site._writeProfilerMark("_measureElements,StartTM");

                                var surface = that._createMeasuringSurface(),
                                    itemsContainer = _Global.document.createElement("div"),
                                    site = that._site,
                                    measuringElements = that._measuringElements,
                                    elementsToMeasure = that._elementsToMeasure,
                                    stopMeasuring = false;

                                itemsContainer.className = _Constants._itemsContainerClass + " " + _Constants._laidOutClass;
                                // This code is executed by CellSpanningGroups where styling is configured for –ms-grid. Let's satisfy these assumptions
                                itemsContainer.style.cssText +=
                                        ";display: -ms-grid" +
                                        ";-ms-grid-column: 1" +
                                        ";-ms-grid-row: 1";

                                var keys = Object.keys(elementsToMeasure),
                                    len,
                                    i;

                                for (i = 0, len = keys.length; i < len; i++) {
                                    var element = elementsToMeasure[keys[i]].element;
                                    element.style["-ms-grid-column"] = i + 1;
                                    element.style["-ms-grid-row"] = i + 1;
                                    itemsContainer.appendChild(element);
                                }

                                surface.appendChild(itemsContainer);
                                site.viewport.insertBefore(surface, site.viewport.firstChild);

                                // Reading from the DOM may cause the app's resize handler to
                                // be run synchronously which may invalidate this measuring
                                // operation. When this happens, stop measuring.
                                measuringElements.then(null, function () {
                                    stopMeasuring = true;
                                });

                                for (i = 0, len = keys.length; i < len && !stopMeasuring; i++) {
                                    var entry = elementsToMeasure[keys[i]],
                                        item = entry.element.querySelector("." + _Constants._itemClass);

                                    entry.width = _ElementUtilities.getTotalWidth(item);
                                    entry.height = _ElementUtilities.getTotalHeight(item);

                                }

                                if (surface.parentNode) {
                                    surface.parentNode.removeChild(surface);
                                }
                                if (measuringElements === that._measuringElements) {
                                    that._measuringElements = null;
                                }

                                site._writeProfilerMark("_measureElements,StopTM");
                            },
                            function (error) {
                                that._measuringElements = null;
                                return Promise.wrapError(error);
                            }
                        );
                    }
                    return this._measuringElements;
                },

                _ensureEnvInfo: function _LayoutCommon_ensureEnvInfo() {
                    if (!this._envInfo) {
                        this._envInfo = getEnvironmentSupportInformation(this._site);
                        if (this._envInfo && !this._envInfo.supportsCSSGrid) {
                            _ElementUtilities.addClass(this._site.surface, _Constants._noCSSGrid);
                        }
                    }
                    return !!this._envInfo;
                },

                _createMeasuringSurface: function _LayoutCommon_createMeasuringSurface() {
                    var surface = _Global.document.createElement("div");

                    surface.style.cssText =
                        "visibility: hidden" +
                        ";-ms-grid-columns: auto" +
                        ";-ms-grid-rows: auto" +
                        ";-ms-flex-align: start" +
                        ";-webkit-align-items: flex-start" +
                        ";align-items: flex-start";
                    surface.className = _Constants._scrollableClass + " " + (this._inListMode ? _Constants._listLayoutClass : _Constants._gridLayoutClass);
                    if (!this._envInfo.supportsCSSGrid) {
                        _ElementUtilities.addClass(surface, _Constants._noCSSGrid);
                    }
                    if (this._groupsEnabled) {
                        if (this._groupHeaderPosition === HeaderPosition.top) {
                            _ElementUtilities.addClass(surface, _Constants._headerPositionTopClass);
                        } else {
                            _ElementUtilities.addClass(surface, _Constants._headerPositionLeftClass);
                        }
                    }

                    return surface;
                },

                // Assumes that the size of the item at the specified index is representative
                // of the size of all items, measures it, and stores the measurements in
                // this._sizes. If necessary, also:
                // - Creates a CSS rule to give the containers a height and width
                // - Stores the name associated with the rule in this._containerSizeClassName
                // - Adds the class name associated with the rule to the surface
                _measureItem: function _LayoutCommon_measureItem(index) {
                    var that = this;
                    var perfId = "Layout:measureItem";
                    var site = that._site;
                    var measuringPromise = that._measuringPromise;

                    // itemPromise is optional. It is provided when taking a second attempt at measuring.
                    function measureItemImpl(index, itemPromise) {
                        var secondTry = !!itemPromise,
                            elementPromises = {},
                            itemPromise,
                            left = site.rtl ? "right" : "left";

                        return site.itemCount.then(function (count) {
                            if (!count || (that._groupsEnabled && !site.groupCount)) {
                                return Promise.cancel;
                            }

                            itemPromise = itemPromise || site.itemFromIndex(index);
                            elementPromises.container = site.renderItem(itemPromise);
                            if (that._groupsEnabled) {
                                elementPromises.headerContainer = site.renderHeader(that._site.groupFromIndex(site.groupIndexFromItemIndex(index)));
                            }

                            return Promise.join(elementPromises);
                        }).then(function (elements) {

                            // Reading from the DOM is tricky because each read may trigger a resize handler which
                            // may invalidate this layout object. To make it easier to minimize bugs in this edge case:
                            //  1. All DOM reads for _LayoutCommon_measureItem should be contained within this function.
                            //  2. This function should remain as simple as possible. Stick to DOM reads, avoid putting
                            //     logic in here, and cache all needed instance variables at the top of the function.
                            //
                            // Returns null if the measuring operation was invalidated while reading from the DOM.
                            // Otherwise, returns an object containing the measurements.
                            function readMeasurementsFromDOM() {
                                var horizontal = that._horizontal;
                                var groupsEnabled = that._groupsEnabled;
                                var stopMeasuring = false;

                                // Reading from the DOM may cause the app's resize handler to
                                // be run synchronously which may invalidate this measuring
                                // operation. When this happens, stop measuring.
                                measuringPromise.then(null, function () {
                                    stopMeasuring = true;
                                });

                                var firstElementOnSurfaceMargins = getMargins(firstElementOnSurface);
                                var firstElementOnSurfaceOffsetX = site.rtl ?
                                    (site.viewport.offsetWidth - (firstElementOnSurface.offsetLeft + firstElementOnSurface.offsetWidth)) :
                                    firstElementOnSurface.offsetLeft;
                                var firstElementOnSurfaceOffsetY = firstElementOnSurface.offsetTop;

                                var sizes = {
                                    // These will be set by _viewportSizeChanged
                                    viewportContentSize: 0,
                                    surfaceContentSize: 0,
                                    maxItemsContainerContentSize: 0,

                                    surfaceOuterHeight: getOuterHeight(surface),
                                    surfaceOuterWidth: getOuterWidth(surface),

                                    // Origin of the grid layout's content in viewport coordinates
                                    layoutOriginX: firstElementOnSurfaceOffsetX - firstElementOnSurfaceMargins[left],
                                    layoutOriginY: firstElementOnSurfaceOffsetY - firstElementOnSurfaceMargins.top,
                                    itemsContainerOuterHeight: getOuterHeight(itemsContainer),
                                    itemsContainerOuterWidth: getOuterWidth(itemsContainer),
                                    // Amount of space between the items container's margin and its content
                                    itemsContainerOuterX: getOuter(site.rtl ? "Right" : "Left", itemsContainer),
                                    itemsContainerOuterY: getOuter("Top", itemsContainer),
                                    itemsContainerMargins: getMargins(itemsContainer),

                                    itemBoxOuterHeight: getOuterHeight(itemBox),
                                    itemBoxOuterWidth: getOuterWidth(itemBox),
                                    containerOuterHeight: getOuterHeight(elements.container),
                                    containerOuterWidth: getOuterWidth(elements.container),
                                    emptyContainerContentHeight: _ElementUtilities.getContentHeight(emptyContainer),
                                    emptyContainerContentWidth: _ElementUtilities.getContentWidth(emptyContainer),

                                    containerMargins: getMargins(elements.container),
                                    // containerWidth/Height are computed when a uniform group is detected
                                    containerWidth: 0,
                                    containerHeight: 0,
                                    // true when both containerWidth and containerHeight have been measured
                                    containerSizeLoaded: false
                                };

                                if (groupsEnabled) {
                                    // Amount of space between the header container's margin and its content
                                    sizes.headerContainerOuterX = getOuter(site.rtl ? "Right" : "Left", elements.headerContainer),
                                    sizes.headerContainerOuterY = getOuter("Top", elements.headerContainer),

                                    sizes.headerContainerOuterWidth = getOuterWidth(elements.headerContainer);
                                    sizes.headerContainerOuterHeight = getOuterHeight(elements.headerContainer);
                                    sizes.headerContainerWidth = _ElementUtilities.getTotalWidth(elements.headerContainer);
                                    sizes.headerContainerHeight = _ElementUtilities.getTotalHeight(elements.headerContainer);
                                    sizes.headerContainerMinWidth = getDimension(elements.headerContainer, "minWidth") + sizes.headerContainerOuterWidth;
                                    sizes.headerContainerMinHeight = getDimension(elements.headerContainer, "minHeight") + sizes.headerContainerOuterHeight;
                                }

                                var measurements = {
                                    // Measurements which are needed after measureItem has returned.
                                    sizes: sizes,

                                    // Measurements which are only needed within measureItem.
                                    viewportContentWidth: _ElementUtilities.getContentWidth(site.viewport),
                                    viewportContentHeight: _ElementUtilities.getContentHeight(site.viewport),
                                    containerContentWidth: _ElementUtilities.getContentWidth(elements.container),
                                    containerContentHeight: _ElementUtilities.getContentHeight(elements.container),
                                    containerWidth: _ElementUtilities.getTotalWidth(elements.container),
                                    containerHeight: _ElementUtilities.getTotalHeight(elements.container)
                                };
                                measurements.viewportCrossSize = measurements[horizontal ? "viewportContentHeight" : "viewportContentWidth"];

                                site.readyToMeasure();

                                return stopMeasuring ? null : measurements;
                            }

                            function cleanUp() {
                                if (surface.parentNode) {
                                    surface.parentNode.removeChild(surface);
                                }
                            }

                            var surface = that._createMeasuringSurface(),
                                itemsContainer = _Global.document.createElement("div"),
                                emptyContainer = _Global.document.createElement("div"),
                                itemBox = elements.container.querySelector("." + _Constants._itemBoxClass),
                                groupIndex = site.groupIndexFromItemIndex(index);

                            emptyContainer.className = _Constants._containerClass;
                            itemsContainer.className = _Constants._itemsContainerClass + " " + _Constants._laidOutClass;
                            // Use display=inline-block so that the width sizes to content when not in list mode.
                            // When in grid mode, put items container and header container in different rows and columns so that the size of the items container does not affect the size of the header container and vice versa.
                            // Use the same for list mode when headers are inline with item containers.
                            // When headers are to the left of a vertical list, or above a horizontal list, put the rows/columns they would be in when laid out normally
                            // into the CSS text for measuring. We have to do this because list item containers should take up 100% of the space left over in the surface
                            // once the group's header is laid out.
                            var itemsContainerRow = 1,
                                itemsContainerColumn = 1,
                                headerContainerRow = 2,
                                headerContainerColumn = 2,
                                firstElementOnSurface = itemsContainer,
                                addHeaderFirst = false;
                            if (that._inListMode && that._groupsEnabled) {
                                if (that._horizontal && that._groupHeaderPosition === HeaderPosition.top) {
                                    itemsContainerRow = 2;
                                    headerContainerColumn = 1;
                                    headerContainerRow = 1;
                                    firstElementOnSurface = elements.headerContainer;
                                    addHeaderFirst = true;
                                } else if (!that._horizontal && that._groupHeaderPosition === HeaderPosition.left) {
                                    itemsContainerColumn = 2;
                                    headerContainerColumn = 1;
                                    headerContainerRow = 1;
                                    firstElementOnSurface = elements.headerContainer;
                                    addHeaderFirst = true;
                                }
                            }
                            // ListMode needs to use display block to proprerly measure items in vertical mode, and display flex to properly measure items in horizontal mode
                            itemsContainer.style.cssText +=
                                    ";display: " + (that._inListMode ? ((that._horizontal ? "flex" : "block") + "; overflow: hidden") : "inline-block") +
                                     ";vertical-align:top" +
                                    ";-ms-grid-column: " + itemsContainerColumn +
                                    ";-ms-grid-row: " + itemsContainerRow;
                            if (!that._inListMode) {
                                elements.container.style.display = "inline-block";
                            }
                            if (that._groupsEnabled) {
                                elements.headerContainer.style.cssText +=
                                    ";display: inline-block" +
                                    ";-ms-grid-column: " + headerContainerColumn +
                                    ";-ms-grid-row: " + headerContainerRow;
                                _ElementUtilities.addClass(elements.headerContainer, _Constants._laidOutClass + " " + _Constants._groupLeaderClass);
                                if ((that._groupHeaderPosition === HeaderPosition.top && that._horizontal) ||
                                    (that._groupHeaderPosition === HeaderPosition.left && !that._horizontal)) {
                                    _ElementUtilities.addClass(itemsContainer, _Constants._groupLeaderClass);
                                }
                            }
                            if (addHeaderFirst) {
                                surface.appendChild(elements.headerContainer);
                            }

                            itemsContainer.appendChild(elements.container);
                            itemsContainer.appendChild(emptyContainer);

                            surface.appendChild(itemsContainer);
                            if (!addHeaderFirst && that._groupsEnabled) {
                                surface.appendChild(elements.headerContainer);
                            }
                            site.viewport.insertBefore(surface, site.viewport.firstChild);

                            var measurements = readMeasurementsFromDOM();

                            if (!measurements) {
                                // While reading from the DOM, the measuring operation was invalidated. Bail out.
                                cleanUp();
                                return Promise.cancel;
                            } else if ((that._horizontal && measurements.viewportContentHeight === 0) || (!that._horizontal && measurements.viewportContentWidth === 0)) {
                                // ListView is invisible so we can't measure. Return a canceled promise.
                                cleanUp();
                                return Promise.cancel;
                            } else if (!secondTry && !that._isCellSpanning(groupIndex) &&
                                    (measurements.containerContentWidth === 0 || measurements.containerContentHeight === 0)) {
                                // win-container has no size. For backwards compatibility, wait for the item promise and then try measuring again.
                                cleanUp();
                                return itemPromise.then(function () {
                                    return measureItemImpl(index, itemPromise);
                                });
                            } else {
                                var sizes = that._sizes = measurements.sizes;

                                // Wrappers for orientation-specific properties.
                                // Sizes prefaced with "cross" refer to the sizes orthogonal to the current layout orientation. Sizes without a preface are in the orientation's direction.
                                Object.defineProperties(sizes, {
                                    surfaceOuterCrossSize: {
                                        get: function () {
                                            return (that._horizontal ? sizes.surfaceOuterHeight : sizes.surfaceOuterWidth);
                                        },
                                        enumerable: true
                                    },
                                    layoutOrigin: {
                                        get: function () {
                                            return (that._horizontal ? sizes.layoutOriginX : sizes.layoutOriginY);
                                        },
                                        enumerable: true
                                    },
                                    itemsContainerOuterSize: {
                                        get: function () {
                                            return (that._horizontal ? sizes.itemsContainerOuterWidth : sizes.itemsContainerOuterHeight);
                                        },
                                        enumerable: true
                                    },
                                    itemsContainerOuterCrossSize: {
                                        get: function () {
                                            return (that._horizontal ? sizes.itemsContainerOuterHeight : sizes.itemsContainerOuterWidth);
                                        },
                                        enumerable: true
                                    },
                                    itemsContainerOuterStart: {
                                        get: function () {
                                            return (that._horizontal ? sizes.itemsContainerOuterX : sizes.itemsContainerOuterY);
                                        },
                                        enumerable: true
                                    },
                                    itemsContainerOuterCrossStart: {
                                        get: function () {
                                            return (that._horizontal ? sizes.itemsContainerOuterY : sizes.itemsContainerOuterX);
                                        },
                                        enumerable: true
                                    },
                                    containerCrossSize: {
                                        get: function () {
                                            return (that._horizontal ? sizes.containerHeight : sizes.containerWidth);
                                        },
                                        enumerable: true
                                    },
                                    containerSize: {
                                        get: function () {
                                            return (that._horizontal ? sizes.containerWidth : sizes.containerHeight);
                                        },
                                        enumerable: true
                                    },
                                });

                                // If the measured group is uniform, measure the container height
                                // and width now. Otherwise, compute them thru itemInfo on demand (via _ensureContainerSize).
                                if (!that._isCellSpanning(groupIndex)) {
                                    if (that._inListMode) {
                                        var itemsContainerContentSize = measurements.viewportCrossSize - sizes.surfaceOuterCrossSize - that._getHeaderSizeContentAdjustment() - sizes.itemsContainerOuterCrossSize;
                                        if (that._horizontal) {
                                            sizes.containerHeight = itemsContainerContentSize;
                                            sizes.containerWidth = measurements.containerWidth;
                                        } else {
                                            sizes.containerHeight = measurements.containerHeight;
                                            sizes.containerWidth = itemsContainerContentSize;
                                        }
                                    } else {
                                        sizes.containerWidth = measurements.containerWidth;
                                        sizes.containerHeight = measurements.containerHeight;
                                    }
                                    sizes.containerSizeLoaded = true;
                                }

                                that._createContainerStyleRule();
                                that._viewportSizeChanged(measurements.viewportCrossSize);

                                cleanUp();
                            }
                        });
                    }

                    if (!measuringPromise) {
                        site._writeProfilerMark(perfId + ",StartTM");
                        // Use a signal to guarantee that measuringPromise is set before the promise
                        // handler is executed (measuringPromise is referenced within measureItemImpl).
                        var promiseStoredSignal = new _Signal();
                        that._measuringPromise = measuringPromise = promiseStoredSignal.promise.then(function () {
                            if (that._ensureEnvInfo()) {
                                return measureItemImpl(index);
                            } else {
                                // Couldn't get envInfo. ListView is invisible. Bail out.
                                return Promise.cancel;
                            }
                        }).then(function () {
                            site._writeProfilerMark(perfId + ":complete,info");
                            site._writeProfilerMark(perfId + ",StopTM");
                        }, function (error) {
                            // The purpose of the measuring promise is so that we only
                            // measure once. If measuring fails, clear the promise because
                            // we still need to measure.
                            that._measuringPromise = null;

                            site._writeProfilerMark(perfId + ":canceled,info");
                            site._writeProfilerMark(perfId + ",StopTM");

                            return Promise.wrapError(error);
                        });
                        promiseStoredSignal.complete();
                    }
                    return measuringPromise;
                },

                _getHeaderSizeGroupAdjustment: function () {
                    if (this._groupsEnabled) {
                        if (this._horizontal && this._groupHeaderPosition === HeaderPosition.left) {
                            return this._sizes.headerContainerWidth;
                        } else if (!this._horizontal && this._groupHeaderPosition === HeaderPosition.top) {
                            return this._sizes.headerContainerHeight;
                        }
                    }

                    return 0;
                },
                _getHeaderSizeContentAdjustment: function () {
                    if (this._groupsEnabled) {
                        if (this._horizontal && this._groupHeaderPosition === HeaderPosition.top) {
                            return this._sizes.headerContainerHeight;
                        } else if (!this._horizontal && this._groupHeaderPosition === HeaderPosition.left) {
                            return this._sizes.headerContainerWidth;
                        }
                    }

                    return 0;
                },

                // Horizontal layouts lay items out top to bottom, left to right, whereas vertical layouts lay items out left to right, top to bottom.
                // The viewport size is the size layouts use to determine how many items can be placed in one bar, so it should be cross to the
                // orientation.
                _getViewportCrossSize: function () {
                    return this._site.viewportSize[this._horizontal ? "height" : "width"];
                },

                // viewportContentSize is the new viewport size
                _viewportSizeChanged: function _LayoutCommon_viewportSizeChanged(viewportContentSize) {
                    var sizes = this._sizes;

                    sizes.viewportContentSize = viewportContentSize;
                    sizes.surfaceContentSize = viewportContentSize - sizes.surfaceOuterCrossSize;
                    sizes.maxItemsContainerContentSize = sizes.surfaceContentSize - sizes.itemsContainerOuterCrossSize - this._getHeaderSizeContentAdjustment();

                    // This calculation is for uniform layouts
                    if (sizes.containerSizeLoaded && !this._inListMode) {
                        this._itemsPerBar = Math.floor(sizes.maxItemsContainerContentSize / sizes.containerCrossSize);
                        if (this.maximumRowsOrColumns) {
                            this._itemsPerBar = Math.min(this._itemsPerBar, this.maximumRowsOrColumns);
                        }
                        this._itemsPerBar = Math.max(1, this._itemsPerBar);
                    } else {
                        if (this._inListMode) {
                            sizes[this._horizontal ? "containerHeight" : "containerWidth"] = sizes.maxItemsContainerContentSize;
                        }
                        this._itemsPerBar = 1;
                    }

                    // Ignore animations if height changed
                    this._resetAnimationCaches();
                },

                _createContainerStyleRule: function _LayoutCommon_createContainerStyleRule() {
                    // Adding CSS rules is expensive. Add a rule to provide a
                    // height and width for containers if the app hasn't provided one.
                    var sizes = this._sizes;
                    if (!this._containerSizeClassName && sizes.containerSizeLoaded && (sizes.emptyContainerContentHeight === 0 || sizes.emptyContainerContentWidth === 0)) {
                        var width = sizes.containerWidth - sizes.containerOuterWidth + "px",
                            height = sizes.containerHeight - sizes.containerOuterHeight + "px";
                        if (this._inListMode) {
                            if (this._horizontal) {
                                height = "calc(100% - " + (sizes.containerMargins.top + sizes.containerMargins.bottom) + "px)";
                            } else {
                                width = "auto";
                            }
                        }

                        if (!this._containerSizeClassName) {
                            this._containerSizeClassName = uniqueCssClassName("containersize");
                            _ElementUtilities.addClass(this._site.surface, this._containerSizeClassName);
                        }
                        var ruleSelector = "." + _Constants._containerClass,
                            ruleBody = "width:" + width + ";height:" + height + ";";
                        addDynamicCssRule(this._containerSizeClassName, this._site, ruleSelector, ruleBody);
                    }
                },

                // Computes container width and height if they haven't been computed yet. This
                // should happen when the first uniform group is created.
                _ensureContainerSize: function _LayoutCommon_ensureContainerSize(group) {
                    var sizes = this._sizes;
                    if (!sizes.containerSizeLoaded && !this._ensuringContainerSize) {
                        var promise;
                        if ((!this._itemInfo || typeof this._itemInfo !== "function") && this._useDefaultItemInfo) {
                            var margins = sizes.containerMargins;
                            promise = Promise.wrap({
                                width: group.groupInfo.cellWidth - margins.left - margins.right,
                                height: group.groupInfo.cellHeight - margins.top - margins.bottom
                            });

                        } else {
                            promise = this._getItemInfo();
                        }

                        var that = this;
                        this._ensuringContainerSize = promise.then(function (itemSize) {
                            sizes.containerSizeLoaded = true;
                            sizes.containerWidth = itemSize.width + sizes.itemBoxOuterWidth + sizes.containerOuterWidth;
                            sizes.containerHeight = itemSize.height + sizes.itemBoxOuterHeight + sizes.containerOuterHeight;
                            if (!that._inListMode) {
                                that._itemsPerBar = Math.floor(sizes.maxItemsContainerContentSize / sizes.containerCrossSize);
                                if (that.maximumRowsOrColumns) {
                                    that._itemsPerBar = Math.min(that._itemsPerBar, that.maximumRowsOrColumns);
                                }
                                that._itemsPerBar = Math.max(1, that._itemsPerBar);
                            } else {
                                that._itemsPerBar = 1;
                            }
                            that._createContainerStyleRule();
                        });

                        promise.done(
                            function () {
                                that._ensuringContainerSize = null;
                            },
                            function () {
                                that._ensuringContainerSize = null;
                            }
                        );

                        return promise;
                    } else {
                        return this._ensuringContainerSize ? this._ensuringContainerSize : Promise.wrap();
                    }
                },

                _indexToCoordinate: function _LayoutCommon_indexToCoordinate(index, itemsPerBar) {
                    itemsPerBar = itemsPerBar || this._itemsPerBar;
                    var bar = Math.floor(index / itemsPerBar);
                    if (this._horizontal) {
                        return {
                            column: bar,
                            row: index - bar * itemsPerBar
                        };
                    } else {
                        return {
                            row: bar,
                            column: index - bar * itemsPerBar
                        };
                    }
                },

                // Empty ranges are represented by null. Non-empty ranges are represented by
                // an object with 2 properties: firstIndex and lastIndex.
                _rangeForGroup: function _LayoutCommon_rangeForGroup(group, range) {
                    var first = group.startIndex,
                        last = first + group.count - 1;

                    if (!range || range.firstIndex > last || range.lastIndex < first) {
                        // There isn't any overlap between range and the group's indices
                        return null;
                    } else {
                        return {
                            firstIndex: Math.max(0, range.firstIndex - first),
                            lastIndex: Math.min(group.count - 1, range.lastIndex - first)
                        };
                    }
                },

                _syncDomWithGroupHeaderPosition: function _LayoutCommon_syncDomWithGroupHeaderPosition(tree) {
                    if (this._groupsEnabled && this._oldGroupHeaderPosition !== this._groupHeaderPosition) {
                        // this._oldGroupHeaderPosition may refer to top, left, or null. It will be null
                        // the first time this function is called which means that no styles have to be
                        // removed.

                        var len = tree.length,
                            i;
                        // Remove styles associated with old group header position
                        if (this._oldGroupHeaderPosition === HeaderPosition.top) {
                            _ElementUtilities.removeClass(this._site.surface, _Constants._headerPositionTopClass);
                            // maxWidth must be cleared because it is used with headers in the top position but not the left position.
                            // The _groupLeaderClass must be removed from the itemsContainer element because the associated styles
                            // should only be applied to it when headers are in the top position.
                            if (this._horizontal) {
                                for (i = 0; i < len; i++) {
                                    tree[i].header.style.maxWidth = "";
                                    _ElementUtilities.removeClass(tree[i].itemsContainer.element, _Constants._groupLeaderClass);
                                }
                            } else {
                                this._site.surface.style.msGridRows = "";
                            }
                        } else if (this._oldGroupHeaderPosition === HeaderPosition.left) {
                            _ElementUtilities.removeClass(this._site.surface, _Constants._headerPositionLeftClass);
                            // msGridColumns is cleared for a similar reason as maxWidth
                            if (!this._horizontal) {
                                for (i = 0; i < len; i++) {
                                    tree[i].header.style.maxHeight = "";
                                    _ElementUtilities.removeClass(tree[i].itemsContainer.element, _Constants._groupLeaderClass);
                                }
                            }
                            this._site.surface.style.msGridColumns = "";
                        }

                        // Add styles associated with new group header position
                        if (this._groupHeaderPosition === HeaderPosition.top) {
                            _ElementUtilities.addClass(this._site.surface, _Constants._headerPositionTopClass);
                            if (this._horizontal) {
                                for (i = 0; i < len; i++) {
                                    _ElementUtilities.addClass(tree[i].itemsContainer.element, _Constants._groupLeaderClass);
                                }
                            }
                        } else {
                            _ElementUtilities.addClass(this._site.surface, _Constants._headerPositionLeftClass);
                            if (!this._horizontal) {
                                for (i = 0; i < len; i++) {
                                    _ElementUtilities.addClass(tree[i].itemsContainer.element, _Constants._groupLeaderClass);
                                }
                            }
                        }

                        this._oldGroupHeaderPosition = this._groupHeaderPosition;
                    }
                },

                _layoutGroup: function _LayoutCommon_layoutGroup(index) {
                    var group = this._groups[index],
                        groupBundle = this._site.tree[index],
                        headerContainer = groupBundle.header,
                        itemsContainer = groupBundle.itemsContainer.element,
                        sizes = this._sizes,
                        groupCrossSize = group.getItemsContainerCrossSize();

                    if (this._groupsEnabled) {
                        if (this._horizontal) {
                            if (this._groupHeaderPosition === HeaderPosition.top) {
                                // Horizontal with headers above
                                //
                                var headerContainerMinContentWidth = sizes.headerContainerMinWidth - sizes.headerContainerOuterWidth,
                                    itemsContainerContentWidth = group.getItemsContainerSize() - sizes.headerContainerOuterWidth;
                                headerContainer.style.maxWidth = Math.max(headerContainerMinContentWidth, itemsContainerContentWidth) + "px";
                                if (this._envInfo.supportsCSSGrid) {
                                    headerContainer.style.msGridColumn = index + 1;
                                    itemsContainer.style.msGridColumn = index + 1;
                                } else {
                                    headerContainer.style.height = (sizes.headerContainerHeight - sizes.headerContainerOuterHeight) + "px";
                                    itemsContainer.style.height = (groupCrossSize - sizes.itemsContainerOuterHeight) + "px";
                                    // If the itemsContainer is too small, the next group's header runs the risk of appearing below the current group's items.
                                    // We need to add a margin to the bottom of the itemsContainer to prevent that from happening.
                                    itemsContainer.style.marginBottom = sizes.itemsContainerMargins.bottom + (sizes.maxItemsContainerContentSize - groupCrossSize + sizes.itemsContainerOuterHeight) + "px";
                                }
                                // itemsContainers only get the _groupLeaderClass when header position is top.
                                _ElementUtilities.addClass(itemsContainer, _Constants._groupLeaderClass);
                            } else {
                                // Horizontal with headers on the left
                                //
                                if (this._envInfo.supportsCSSGrid) {
                                    headerContainer.style.msGridColumn = index * 2 + 1;
                                    itemsContainer.style.msGridColumn = index * 2 + 2;
                                } else {
                                    headerContainer.style.width = sizes.headerContainerWidth - sizes.headerContainerOuterWidth + "px";
                                    headerContainer.style.height = (groupCrossSize - sizes.headerContainerOuterHeight) + "px";
                                    itemsContainer.style.height = (groupCrossSize - sizes.itemsContainerOuterHeight) + "px";
                                }
                            }
                        } else {
                            if (this._groupHeaderPosition === HeaderPosition.left) {
                                // Vertical with headers on the left
                                //
                                var headerContainerMinContentHeight = sizes.headerContainerMinHeight - sizes.headerContainerOuterHeight,
                                    itemsContainerContentHeight = group.getItemsContainerSize() - sizes.headerContainerOuterHeight;
                                headerContainer.style.maxHeight = Math.max(headerContainerMinContentHeight, itemsContainerContentHeight) + "px";
                                if (this._envInfo.supportsCSSGrid) {
                                    headerContainer.style.msGridRow = index + 1;
                                    itemsContainer.style.msGridRow = index + 1;
                                } else {
                                    headerContainer.style.width = (sizes.headerContainerWidth - sizes.headerContainerOuterWidth) + "px";
                                    itemsContainer.style.width = (groupCrossSize - sizes.itemsContainerOuterWidth) + "px";
                                    // If the itemsContainer is too small, the next group's header runs the risk of appearing to the side of the current group's items.
                                    // We need to add a margin to the right of the itemsContainer to prevent that from happening (or the left margin, in RTL).
                                    itemsContainer.style["margin" + (this._site.rtl ? "Left" : "Right")] = (sizes.itemsContainerMargins[(this._site.rtl ? "left" : "right")] +
                                        (sizes.maxItemsContainerContentSize - groupCrossSize + sizes.itemsContainerOuterWidth)) + "px";
                                }
                                // itemsContainers only get the _groupLeaderClass when header position is left.
                                _ElementUtilities.addClass(itemsContainer, _Constants._groupLeaderClass);
                            } else {
                                // Vertical with headers above
                                //
                                headerContainer.style.msGridRow = index * 2 + 1;
                                // It's important to explicitly set the container height in vertical list mode with headers above, since we use flow layout.
                                // When the header's content is taken from the DOM, the headerContainer will shrink unless it has a height set.
                                if (this._inListMode) {
                                    headerContainer.style.height = (sizes.headerContainerHeight - sizes.headerContainerOuterHeight) + "px";
                                } else {
                                    if (this._envInfo.supportsCSSGrid) {
                                        itemsContainer.style.msGridRow = index * 2 + 2;
                                    } else {
                                        headerContainer.style.height = sizes.headerContainerHeight - sizes.headerContainerOuterHeight + "px";
                                        headerContainer.style.width = (groupCrossSize - sizes.headerContainerOuterWidth) + "px";
                                        itemsContainer.style.width = (groupCrossSize - sizes.itemsContainerOuterWidth) + "px";
                                    }
                                }
                            }

                        }
                        // Header containers always get the _groupLeaderClass.
                        _ElementUtilities.addClass(headerContainer, _Constants._laidOutClass + " " + _Constants._groupLeaderClass);
                    }
                    _ElementUtilities.addClass(itemsContainer, _Constants._laidOutClass);
                }
            }, {
                // The maximum number of rows or columns of win-containers to put into each items block.
                // A row/column cannot be split across multiple items blocks. win-containers
                // are grouped into items blocks in order to mitigate the costs of the platform doing
                // a layout in response to insertions and removals of win-containers.
                _barsPerItemsBlock: 4
            });
        }),

        //
        // Layouts
        //

        _LegacyLayout: _Base.Namespace._lazy(function () {
            return _Base.Class.derive(exports._LayoutCommon, null, {
                /// <field type="Boolean" locid="WinJS.UI._LegacyLayout.disableBackdrop" helpKeyword="WinJS.UI._LegacyLayout.disableBackdrop">
                /// Gets or sets a value that indicates whether the layout should disable the backdrop feature
                /// which avoids blank areas while panning in a virtualized list.
                /// <deprecated type="deprecate">
                /// disableBackdrop is deprecated. Style: .win-listview .win-container.win-backdrop { background-color:transparent; } instead.
                /// </deprecated>
                /// </field>
                disableBackdrop: {
                    get: function _LegacyLayout_disableBackdrop_get() {
                        return this._backdropDisabled || false;
                    },
                    set: function _LegacyLayout_disableBackdrop_set(value) {
                        _ElementUtilities._deprecated(_ErrorMessages.disableBackdropIsDeprecated);
                        value = !!value;
                        if (this._backdropDisabled !== value) {
                            this._backdropDisabled = value;
                            if (this._disableBackdropClassName) {
                                deleteDynamicCssRule(this._disableBackdropClassName);
                                this._site && _ElementUtilities.removeClass(this._site.surface, this._disableBackdropClassName);
                                this._disableBackdropClassName = null;
                            }
                            this._disableBackdropClassName = uniqueCssClassName("disablebackdrop");
                            this._site && _ElementUtilities.addClass(this._site.surface, this._disableBackdropClassName);
                            if (value) {
                                var ruleSelector = ".win-container.win-backdrop",
                                    ruleBody = "background-color:transparent;";
                                addDynamicCssRule(this._disableBackdropClassName, this._site, ruleSelector, ruleBody);
                            }
                        }
                    }
                },

                /// <field type="String" locid="WinJS.UI._LegacyLayout.backdropColor" helpKeyword="WinJS.UI._LegacyLayout.backdropColor">
                /// Gets or sets the fill color for the default pattern used for the backdrops.
                /// The default value is "rgba(155,155,155,0.23)".
                /// <deprecated type="deprecate">
                /// backdropColor is deprecated. Style: .win-listview .win-container.win-backdrop { rgba(155,155,155,0.23); } instead.
                /// </deprecated>
                /// </field>
                backdropColor: {
                    get: function _LegacyLayout_backdropColor_get() {
                        return this._backdropColor || "rgba(155,155,155,0.23)";
                    },
                    set: function _LegacyLayout_backdropColor_set(value) {
                        _ElementUtilities._deprecated(_ErrorMessages.backdropColorIsDeprecated);
                        if (value && this._backdropColor !== value) {
                            this._backdropColor = value;
                            if (this._backdropColorClassName) {
                                deleteDynamicCssRule(this._backdropColorClassName);
                                this._site && _ElementUtilities.removeClass(this._site.surface, this._backdropColorClassName);
                                this._backdropColorClassName = null;
                            }
                            this._backdropColorClassName = uniqueCssClassName("backdropcolor");
                            this._site && _ElementUtilities.addClass(this._site.surface, this._backdropColorClassName);
                            var ruleSelector = ".win-container.win-backdrop",
                                ruleBody = "background-color:" + value + ";";
                            addDynamicCssRule(this._backdropColorClassName, this._site, ruleSelector, ruleBody);
                        }
                    }
                }
            });
        }),

        GridLayout: _Base.Namespace._lazy(function () {
            return _Base.Class.derive(exports._LegacyLayout, function (options) {
                /// <signature helpKeyword="WinJS.UI.GridLayout">
                /// <summary locid="WinJS.UI.GridLayout">
                /// Creates a new GridLayout.
                /// </summary>
                /// <param name="options" type="Object" locid="WinJS.UI.GridLayout_p:options">
                /// An object that contains one or more property/value pairs to apply to the new control. Each property of the options
                /// object corresponds to one of the control's properties or events.
                /// </param>
                /// <returns type="WinJS.UI.GridLayout" locid="WinJS.UI.GridLayout_returnValue">
                /// The new GridLayout.
                /// </returns>
                /// </signature>
                options = options || {};
                // Executing setters to display compatibility warning
                this.itemInfo = options.itemInfo;
                this.groupInfo = options.groupInfo;
                this._maxRowsOrColumns = 0;
                this._useDefaultItemInfo = true;
                this._elementsToMeasure = {};
                this._groupHeaderPosition = options.groupHeaderPosition || HeaderPosition.top;
                this.orientation = options.orientation || "horizontal";

                if (options.maxRows) {
                    this.maxRows = +options.maxRows;
                }
                if (options.maximumRowsOrColumns) {
                    this.maximumRowsOrColumns = +options.maximumRowsOrColumns;
                }
            }, {

                // Public

                /// <field type="Number" integer="true" locid="WinJS.UI.GridLayout.maximumRowsOrColumns" helpKeyword="WinJS.UI.GridLayout.maximumRowsOrColumns">
                /// Gets the maximum number of rows or columns, depending on the orientation, that should present before it introduces wrapping to the layout.
                /// A value of 0 indicates that there is no maximum. The default value is 0.
                /// </field>
                maximumRowsOrColumns: {
                    get: function () {
                        return this._maxRowsOrColumns;
                    },
                    set: function (value) {
                        this._setMaxRowsOrColumns(value);
                    }
                },

                /// <field type="Number" integer="true" locid="WinJS.UI.GridLayout.maxRows" helpKeyword="WinJS.UI.GridLayout.maxRows">
                /// Gets or sets the maximum number of rows displayed by the ListView.
                /// <deprecated type="deprecate">
                /// WinJS.UI.GridLayout.maxRows may be altered or unavailable after the Windows Library for JavaScript 2.0. Instead, use the maximumRowsOrColumns property.
                /// </deprecated>
                /// </field>
                maxRows: {
                    get: function () {
                        return this.maximumRowsOrColumns;
                    },
                    set: function (maxRows) {
                        _ElementUtilities._deprecated(_ErrorMessages.maxRowsIsDeprecated);
                        this.maximumRowsOrColumns = maxRows;
                    }
                },

                /// <field type="Function" locid="WinJS.UI.GridLayout.itemInfo" helpKeyword="WinJS.UI.GridLayout.itemInfo">
                /// Determines the size of the item and whether
                /// the item should be placed in a new column.
                /// <deprecated type="deprecate">
                /// GridLayout.itemInfo may be altered or unavailable in future versions. Instead, use CellSpanningLayout.
                /// </deprecated>
                /// </field>
                itemInfo: {
                    enumerable: true,
                    get: function () {
                        return this._itemInfo;
                    },
                    set: function (itemInfo) {
                        itemInfo && _ElementUtilities._deprecated(_ErrorMessages.itemInfoIsDeprecated);
                        this._itemInfo = itemInfo;
                        this._invalidateLayout();
                    }
                },

                /// <field type="Function" locid="WinJS.UI.GridLayout.groupInfo" helpKeyword="WinJS.UI.GridLayout.groupInfo">
                /// Indicates whether a group has cell spanning items and specifies the dimensions of the cell.
                /// <deprecated type="deprecate">
                /// GridLayout.groupInfo may be altered or unavailable in future versions. Instead, use CellSpanningLayout.
                /// </deprecated>
                /// </field>
                groupInfo: {
                    enumerable: true,
                    get: function () {
                        return this._groupInfo;
                    },
                    set: function (groupInfo) {
                        groupInfo && _ElementUtilities._deprecated(_ErrorMessages.groupInfoIsDeprecated);
                        this._groupInfo = groupInfo;
                        this._invalidateLayout();
                    }
                }
            });
        })
    });

    var Groups = _Base.Namespace.defineWithParent(null, null, {

        UniformGroupBase: _Base.Namespace._lazy(function () {
            return _Base.Class.define(null, {
                cleanUp: function UniformGroupBase_cleanUp() {
                },

                itemFromOffset: function UniformGroupBase_itemFromOffset(offset, options) {
                    // supported options are:
                    // - wholeItem: when set to true the fully visible item is returned
                    // - last: if 1 the last item is returned. if 0 the first
                    options = options || {};

                    var sizes = this._layout._sizes;

                    // Make offset relative to the items container's content box
                    offset -= sizes.itemsContainerOuterStart;

                    if (options.wholeItem) {
                        offset += (options.last ? -1 : 1) * (sizes.containerSize - 1);
                    }
                    var lastIndexOfGroup = this.count - 1,
                        lastBar = Math.floor(lastIndexOfGroup / this._layout._itemsPerBar),
                        bar = clampToRange(0, lastBar, Math.floor(offset / sizes.containerSize)),
                        index = (bar + options.last) * this._layout._itemsPerBar - options.last;
                    return clampToRange(0, this.count - 1, index);
                },

                hitTest: function UniformGroupBase_hitTest(x, y) {
                    var horizontal = this._layout._horizontal,
                        itemsPerBar = this._layout._itemsPerBar,
                        useListSemantics = this._layout._inListMode || itemsPerBar === 1,
                        directionalLocation = horizontal ? x : y,
                        crossLocation = horizontal ? y : x,
                        sizes = this._layout._sizes;

                    directionalLocation -= sizes.itemsContainerOuterStart;
                    crossLocation -= sizes.itemsContainerOuterCrossStart;

                    var bar = Math.floor(directionalLocation / sizes.containerSize);
                    var slotInBar = clampToRange(0, itemsPerBar - 1, Math.floor(crossLocation / sizes.containerCrossSize));
                    var index = Math.max(-1, bar * itemsPerBar + slotInBar);

                    // insertAfterIndex is determined by which half of the target element the mouse cursor is currently in.
                    // The trouble is that we can cut the element in half horizontally or cut it in half vertically.
                    // Which one we choose depends on the order that elements are laid out in the grid.
                    // A horizontal grid with multiple rows per column will lay items out starting from top to bottom, and move left to right.
                    // A vertical list is just a horizontal grid with an infinite number of rows per column, so it follows the same order.
                    // In both of these cases, each item is cut in half horizontally, since for any item n, n-1 should be above it and n+1 below (ignoring column changes).
                    // A vertical grid lays items out left to right, top to bottom, and a horizontal list left to right (with infinite items per row).
                    // In this case for item n, n-1 is on the left and n+1 on the right, so we cut the item in half vertically.
                    var insertAfterSlot;
                    if ((!horizontal && useListSemantics) ||
                        (horizontal && !useListSemantics)) {
                        insertAfterSlot = (y - sizes.containerHeight / 2) / sizes.containerHeight;
                    } else {
                        insertAfterSlot = (x - sizes.containerWidth / 2) / sizes.containerWidth;
                    }
                    if (useListSemantics) {
                        insertAfterSlot = Math.floor(insertAfterSlot);
                        return {
                            index: index,
                            insertAfterIndex: (insertAfterSlot >= 0 && index >= 0 ? insertAfterSlot : -1)
                        };
                    }
                    insertAfterSlot = clampToRange(-1, itemsPerBar - 1, insertAfterSlot);
                    var insertAfterIndex;
                    if (insertAfterSlot < 0) {
                        insertAfterIndex = bar * itemsPerBar - 1;
                    } else {
                        insertAfterIndex = bar * itemsPerBar + Math.floor(insertAfterSlot);
                    }

                    return {
                        index: clampToRange(-1, this.count - 1, index),
                        insertAfterIndex: clampToRange(-1, this.count - 1, insertAfterIndex)
                    };
                },

                getAdjacent: function UniformGroupBase_getAdjacent(currentItem, pressedKey) {
                    var index = currentItem.index,
                        currentBar = Math.floor(index / this._layout._itemsPerBar),
                        currentSlot = index % this._layout._itemsPerBar,
                        newFocus;

                    switch (pressedKey) {
                        case Key.upArrow:
                            newFocus = (currentSlot === 0 ? "boundary" : index - 1);
                            break;
                        case Key.downArrow:
                            var isLastIndexOfGroup = (index === this.count - 1),
                                inLastSlot = (this._layout._itemsPerBar > 1 && currentSlot === this._layout._itemsPerBar - 1);
                            newFocus = (isLastIndexOfGroup || inLastSlot ? "boundary" : index + 1);
                            break;
                        case Key.leftArrow:
                            newFocus = (currentBar === 0 && this._layout._itemsPerBar > 1 ? "boundary" : index - this._layout._itemsPerBar);
                            break;
                        case Key.rightArrow:
                            var lastIndexOfGroup = this.count - 1,
                                lastBar = Math.floor(lastIndexOfGroup / this._layout._itemsPerBar);
                            newFocus = (currentBar === lastBar ? "boundary" : Math.min(index + this._layout._itemsPerBar, this.count - 1));
                            break;
                    }
                    return (newFocus === "boundary" ? newFocus : { type: _UI.ObjectType.item, index: newFocus });
                },

                getItemsContainerSize: function UniformGroupBase_getItemsContainerSize() {
                    var sizes = this._layout._sizes,
                        barCount = Math.ceil(this.count / this._layout._itemsPerBar);
                    return barCount * sizes.containerSize + sizes.itemsContainerOuterSize;
                },

                getItemsContainerCrossSize: function UniformGroupBase_getItemsContainerCrossSize() {
                    var sizes = this._layout._sizes;
                    return this._layout._itemsPerBar * sizes.containerCrossSize + sizes.itemsContainerOuterCrossSize;
                },

                getItemPositionForAnimations: function UniformGroupBase_getItemPositionForAnimations(itemIndex) {
                    // Top/Left are used to know if the item has moved and also used to position the item if removed.
                    // Row/Column are used to know if a reflow animation should occur
                    // Height/Width are used when positioning a removed item without impacting layout.
                    // The returned rectangle refers to the win-container's border/padding/content box. Coordinates
                    // are relative to group's items container.

                    var sizes = this._layout._sizes;
                    var leftStr = this._layout._site.rtl ? "right" : "left";
                    var containerMargins = this._layout._sizes.containerMargins;
                    var coordinates = this._layout._indexToCoordinate(itemIndex);
                    var itemPosition = {
                        row: coordinates.row,
                        column: coordinates.column,
                        top: containerMargins.top + coordinates.row * sizes.containerHeight,
                        left: containerMargins[leftStr] + coordinates.column * sizes.containerWidth,
                        height: sizes.containerHeight - sizes.containerMargins.top - sizes.containerMargins.bottom,
                        width: sizes.containerWidth - sizes.containerMargins.left - sizes.containerMargins.right
                    };
                    return itemPosition;
                }
            });
        }),

        //
        // Groups for GridLayout
        //
        // Each group implements a 3 function layout interface. The interface is used
        // whenever GridLayout has to do a layout. The interface consists of:
        // - prepareLayout/prepareLayoutWithCopyOfTree: Called 1st. Group should update all of its internal
        //   layout state. It should not modify the DOM. Group should implement either prepareLayout or
        //   prepareLayoutWithCopyOfTree. The former is preferable because the latter is expensive as calling
        //   it requires copying the group's tree. Implementing prepareLayoutWithCopyOfTree is necessary when
        //   the group is manually laying out items and is laying out unrealized items asynchronously
        //   (e.g. CellSpanningGroup). This requires a copy of the tree from the previous layout pass.
        // - layoutRealizedRange: Called 2nd. Group should update the DOM so that
        //   the realized range reflects the internal layout state computed during
        //   prepareLayout.
        // - layoutUnrealizedRange: Called 3rd. Group should update the DOM for the items
        //   outside of the realized range. This function returns a promise so
        //   it can do its work asynchronously. When the promise completes, layout will
        //   be done.
        //
        // The motivation for this interface is perceived performance. If groups had just 1
        // layout function, all items would have to be laid out before any animations could
        // begin. With this interface, animations can begin playing after
        // layoutRealizedRange is called.
        //
        // Each group also implements a cleanUp function which is called when the group is
        // no longer needed so that it can clean up the DOM and its resources. After cleanUp
        // is called, the group object cannnot be reused.
        //

        UniformGroup: _Base.Namespace._lazy(function () {
            return _Base.Class.derive(Groups.UniformGroupBase, function UniformGroup_ctor(layout, itemsContainer) {
                this._layout = layout;
                this._itemsContainer = itemsContainer;
                _ElementUtilities.addClass(this._itemsContainer, layout._inListMode ? _Constants._uniformListLayoutClass : _Constants._uniformGridLayoutClass);
            }, {
                cleanUp: function UniformGroup_cleanUp(skipDomCleanUp) {
                    if (!skipDomCleanUp) {
                        _ElementUtilities.removeClass(this._itemsContainer, _Constants._uniformGridLayoutClass);
                        _ElementUtilities.removeClass(this._itemsContainer, _Constants._uniformListLayoutClass);
                        this._itemsContainer.style.height = this._itemsContainer.style.width = "";
                    }
                    this._itemsContainer = null;
                    this._layout = null;
                    this.groupInfo = null;
                    this.startIndex = null;
                    this.offset = null;
                    this.count = null;
                },

                prepareLayout: function UniformGroup_prepareLayout(itemsCount, oldChangedRealizedRange, oldState, updatedProperties) {
                    this.groupInfo = updatedProperties.groupInfo;
                    this.startIndex = updatedProperties.startIndex;
                    this.count = itemsCount;
                    return this._layout._ensureContainerSize(this);
                },

                layoutRealizedRange: function UniformGroup_layoutRealizedRange() {
                    // Explicitly set the items container size. This is required so that the
                    // surface, which is a grid, will have its width sized to content.
                    var sizes = this._layout._sizes;
                    this._itemsContainer.style[this._layout._horizontal ? "width" : "height"] = this.getItemsContainerSize() - sizes.itemsContainerOuterSize + "px";
                    this._itemsContainer.style[this._layout._horizontal ? "height" : "width"] = (this._layout._inListMode ? sizes.maxItemsContainerContentSize + "px" :
                                                                                                 this._layout._itemsPerBar * sizes.containerCrossSize + "px");
                },

                layoutUnrealizedRange: function UniformGroup_layoutUnrealizedRange() {
                    return Promise.wrap();
                }
            });
        }),

        UniformFlowGroup: _Base.Namespace._lazy(function () {
            return _Base.Class.derive(Groups.UniformGroupBase, function UniformFlowGroup_ctor(layout, tree) {
                this._layout = layout;
                this._itemsContainer = tree.element;
                _ElementUtilities.addClass(this._itemsContainer, layout._inListMode ? _Constants._uniformListLayoutClass : _Constants._uniformGridLayoutClass);
            }, {
                cleanUp: function UniformFlowGroup_cleanUp(skipDomCleanUp) {
                    if (!skipDomCleanUp) {
                        _ElementUtilities.removeClass(this._itemsContainer, _Constants._uniformListLayoutClass);
                        _ElementUtilities.removeClass(this._itemsContainer, _Constants._uniformGridLayoutClass);
                        this._itemsContainer.style.height = "";
                    }
                },
                layout: function UniformFlowGroup_layout() {
                    this._layout._site._writeProfilerMark("Layout:_UniformFlowGroup:setItemsContainerHeight,info");
                    this._itemsContainer.style.height = this.count * this._layout._sizes.containerHeight + "px";
                }
            });
        }),

        CellSpanningGroup: _Base.Namespace._lazy(function () {
            return _Base.Class.define(function CellSpanningGroup_ctor(layout, itemsContainer) {
                this._layout = layout;
                this._itemsContainer = itemsContainer;
                _ElementUtilities.addClass(this._itemsContainer, _Constants._cellSpanningGridLayoutClass);

                this.resetMap();
            }, {
                cleanUp: function CellSpanningGroup_cleanUp(skipDomCleanUp) {
                    if (!skipDomCleanUp) {
                        this._cleanContainers();
                        _ElementUtilities.removeClass(this._itemsContainer, _Constants._cellSpanningGridLayoutClass);
                        this._itemsContainer.style.cssText = "";
                    }
                    this._itemsContainer = null;

                    if (this._layoutPromise) {
                        this._layoutPromise.cancel();
                        this._layoutPromise = null;
                    }
                    this.resetMap();
                    this._slotsPerColumn = null;
                    this._offScreenSlotsPerColumn = null;
                    this._items = null;
                    this._layout = null;
                    this._containersToHide = null;
                    this.groupInfo = null;
                    this.startIndex = null;
                    this.offset = null;
                    this.count = null;
                },

                prepareLayoutWithCopyOfTree: function CellSpanningGroup_prepareLayoutWithCopyOfTree(tree, oldChangedRealizedRange, oldState, updatedProperties) {
                    var that = this;
                    var i;

                    // Remember the items in the old realized item range that changed.
                    // During layoutRealizedRange, they either need to be relaid out or hidden.
                    this._containersToHide = {};
                    if (oldChangedRealizedRange) {
                        for (i = oldChangedRealizedRange.firstIndex; i <= oldChangedRealizedRange.lastIndex; i++) {
                            this._containersToHide[uniqueID(oldState._items[i])] = oldState._items[i];
                        }
                    }

                    // Update public properties
                    this.groupInfo = updatedProperties.groupInfo;
                    this.startIndex = updatedProperties.startIndex;
                    this.count = tree.items.length;

                    this._items = tree.items;
                    this._slotsPerColumn = Math.floor(this._layout._sizes.maxItemsContainerContentSize / this.groupInfo.cellHeight);
                    if (this._layout.maximumRowsOrColumns) {
                        this._slotsPerColumn = Math.min(this._slotsPerColumn, this._layout.maximumRowsOrColumns);
                    }
                    this._slotsPerColumn = Math.max(this._slotsPerColumn, 1);

                    this.resetMap();
                    var itemInfoPromises = new Array(this.count);
                    for (i = 0; i < this.count; i++) {
                        itemInfoPromises[i] = this._layout._getItemInfo(this.startIndex + i);
                    }
                    return Promise.join(itemInfoPromises).then(function (itemInfos) {
                        itemInfos.forEach(function (itemInfo, index) {
                            that.addItemToMap(index, itemInfo);
                        });
                    });
                },

                layoutRealizedRange: function CellSpanningGroup_layoutRealizedRange(firstChangedIndex, realizedRange) {
                    // Lay out the changed items within the realized range
                    if (realizedRange) {
                        var firstChangedRealizedIndex = Math.max(firstChangedIndex, realizedRange.firstIndex),
                            i;
                        for (i = firstChangedRealizedIndex; i <= realizedRange.lastIndex; i++) {
                            this._layoutItem(i);
                            delete this._containersToHide[uniqueID(this._items[i])];
                        }
                    }

                    // Hide the old containers that are in the realized range but weren't relaid out
                    Object.keys(this._containersToHide).forEach(function (id) {
                        _ElementUtilities.removeClass(this._containersToHide[id], _Constants._laidOutClass);
                    }.bind(this));
                    this._containersToHide = {};

                    // Explicitly set the items container height. This is required so that the
                    // surface, which is a grid, will have its width sized to content.
                    this._itemsContainer.style.cssText +=
                        ";width:" + (this.getItemsContainerSize() - this._layout._sizes.itemsContainerOuterSize) +
                        "px;height:" + this._layout._sizes.maxItemsContainerContentSize +
                        "px;-ms-grid-columns: (" + this.groupInfo.cellWidth + "px)[" + this.getColumnCount() +
                        "];-ms-grid-rows: (" + this.groupInfo.cellHeight + "px)[" + (this._slotsPerColumn + this._offScreenSlotsPerColumn) + "]";
                },

                layoutUnrealizedRange: function CellSpanningGroup_layoutUnrealizedRange(firstChangedIndex, realizedRange, beforeRealizedRange) {
                    var that = this;
                    var layoutJob;

                    that._layoutPromise = new Promise(function (complete) {
                        function completeLayout() {
                            layoutJob = null;
                            complete();
                        }

                        function schedule(fn) {
                            return Scheduler.schedule(fn, Scheduler.Priority.normal, null,
                                "WinJS.UI.GridLayout.CellSpanningGroup.LayoutUnrealizedRange");
                        }

                        // These loops are built to lay out the items that are closest to
                        // the realized range first.

                        if (realizedRange) {
                            var stop = false;
                            // For laying out the changed items that are before the realized range
                            var before = realizedRange.firstIndex - 1;
                            // For laying out the changed items that are after the realized range
                            var after = Math.max(firstChangedIndex, realizedRange.lastIndex + 1);
                            after = Math.max(before + 1, after);

                            // Alternate between laying out items before and after the realized range
                            layoutJob = schedule(function unrealizedRangeWork(info) {
                                while (!stop) {
                                    if (info.shouldYield) {
                                        info.setWork(unrealizedRangeWork);
                                        return;
                                    }

                                    stop = true;

                                    if (before >= firstChangedIndex) {
                                        that._layoutItem(before);
                                        before--;
                                        stop = false;
                                    }
                                    if (after < that.count) {
                                        that._layoutItem(after);
                                        after++;
                                        stop = false;
                                    }
                                }
                                completeLayout();
                            });
                        } else if (beforeRealizedRange) {
                            // The items we are laying out come before the realized range.
                            // so lay them out in descending order.
                            var i = that.count - 1;
                            layoutJob = schedule(function beforeRealizedRangeWork(info) {
                                for (; i >= firstChangedIndex; i--) {
                                    if (info.shouldYield) {
                                        info.setWork(beforeRealizedRangeWork);
                                        return;
                                    }
                                    that._layoutItem(i);
                                }
                                completeLayout();
                            });
                        } else {
                            // The items we are laying out come after the realized range
                            // so lay them out in ascending order.
                            var i = firstChangedIndex;
                            layoutJob = schedule(function afterRealizedRangeWork(info) {
                                for (; i < that.count; i++) {
                                    if (info.shouldYield) {
                                        info.setWork(afterRealizedRangeWork);
                                        return;
                                    }
                                    that._layoutItem(i);
                                }
                                completeLayout();
                            });
                        }
                    }, function () {
                        // Cancellation handler for that._layoutPromise
                        layoutJob && layoutJob.cancel();
                        layoutJob = null;
                    });

                    return that._layoutPromise;
                },

                itemFromOffset: function CellSpanningGroup_itemFromOffset(offset, options) {
                    // supported options are:
                    // - wholeItem: when set to true the fully visible item is returned
                    // - last: if 1 the last item is returned. if 0 the first
                    options = options || {};

                    var sizes = this._layout._sizes,
                        margins = sizes.containerMargins;

                    // Make offset relative to the items container's content box
                    offset -= sizes.itemsContainerOuterX;

                    offset -= ((options.last ? 1 : -1) * margins[options.last ? "left" : "right"]);

                    var value = this.indexFromOffset(offset, options.wholeItem, options.last).item;
                    return clampToRange(0, this.count - 1, value);
                },

                getAdjacent: function CellSpanningGroup_getAdjacent(currentItem, pressedKey) {
                    var index,
                        originalIndex;

                    index = originalIndex = currentItem.index;

                    var newIndex, inMap, inMapIndex;
                    if (this.lastAdjacent === index) {
                        inMapIndex = this.lastInMapIndex;
                    } else {
                        inMapIndex = this.findItem(index);
                    }

                    do {
                        var column = Math.floor(inMapIndex / this._slotsPerColumn),
                            row = inMapIndex - column * this._slotsPerColumn,
                            lastColumn = Math.floor((this.occupancyMap.length - 1) / this._slotsPerColumn);

                        switch (pressedKey) {
                            case Key.upArrow:
                                if (row > 0) {
                                    inMapIndex--;
                                } else {
                                    return { type: _UI.ObjectType.item, index: originalIndex };
                                }
                                break;
                            case Key.downArrow:
                                if (row + 1 < this._slotsPerColumn) {
                                    inMapIndex++;
                                } else {
                                    return { type: _UI.ObjectType.item, index: originalIndex };
                                }
                                break;
                            case Key.leftArrow:
                                inMapIndex = (column > 0 ? inMapIndex - this._slotsPerColumn : -1);
                                break;
                            case Key.rightArrow:
                                inMapIndex = (column < lastColumn ? inMapIndex + this._slotsPerColumn : this.occupancyMap.length);
                                break;
                        }

                        inMap = inMapIndex >= 0 && inMapIndex < this.occupancyMap.length;
                        if (inMap) {
                            newIndex = this.occupancyMap[inMapIndex] ? this.occupancyMap[inMapIndex].index : undefined;
                        }

                    } while (inMap && (index === newIndex || newIndex === undefined));

                    this.lastAdjacent = newIndex;
                    this.lastInMapIndex = inMapIndex;

                    return (inMap ? { type: _UI.ObjectType.item, index: newIndex } : "boundary");
                },

                hitTest: function CellSpanningGroup_hitTest(x, y) {
                    var sizes = this._layout._sizes,
                        itemIndex = 0;

                    // Make the coordinates relative to the items container's content box
                    x -= sizes.itemsContainerOuterX;
                    y -= sizes.itemsContainerOuterY;

                    if (this.occupancyMap.length > 0) {
                        var result = this.indexFromOffset(x, false, 0);

                        var counter = Math.min(this._slotsPerColumn - 1, Math.floor(y / this.groupInfo.cellHeight)),
                            curr = result.index,
                            lastValidIndex = curr;
                        while (counter-- > 0) {
                            curr++;
                            if (this.occupancyMap[curr]) {
                                lastValidIndex = curr;
                            }
                        }
                        if (!this.occupancyMap[lastValidIndex]) {
                            lastValidIndex--;
                        }
                        itemIndex = this.occupancyMap[lastValidIndex].index;
                    }

                    var itemSize = this.getItemSize(itemIndex),
                        itemLeft = itemSize.column * this.groupInfo.cellWidth,
                        itemTop = itemSize.row * this.groupInfo.cellHeight,
                        useListSemantics = this._slotsPerColumn === 1,
                        insertAfterIndex = itemIndex;

                    if ((useListSemantics && (x < (itemLeft + itemSize.contentWidth / 2))) ||
                        (!useListSemantics && (y < (itemTop + itemSize.contentHeight / 2)))) {
                        insertAfterIndex--;
                    }

                    return {
                        type: _UI.ObjectType.item,
                        index: clampToRange(0, this.count - 1, itemIndex),
                        insertAfterIndex: clampToRange(-1, this.count - 1, insertAfterIndex)
                    };
                },

                getItemsContainerSize: function CellSpanningGroup_getItemsContainerSize() {
                    var sizes = this._layout._sizes;
                    return this.getColumnCount() * this.groupInfo.cellWidth + sizes.itemsContainerOuterSize;
                },

                getItemsContainerCrossSize: function CellSpanningGroup_getItemsContainerCrossSize() {
                    var sizes = this._layout._sizes;
                    return sizes.maxItemsContainerContentSize + sizes.itemsContainerOuterCrossSize;
                },

                getItemPositionForAnimations: function CellSpanningGroup_getItemPositionForAnimations(itemIndex) {
                    // Top/Left are used to know if the item has moved and also used to position the item if removed.
                    // Row/Column are used to know if a reflow animation should occur
                    // Height/Width are used when positioning a removed item without impacting layout.
                    // The returned rectangle refers to the win-container's border/padding/content box. Coordinates
                    // are relative to group's items container.

                    var leftStr = this._layout._site.rtl ? "right" : "left";
                    var containerMargins = this._layout._sizes.containerMargins;
                    var coordinates = this.getItemSize(itemIndex);
                    var groupInfo = this.groupInfo;
                    var itemPosition = {
                        row: coordinates.row,
                        column: coordinates.column,
                        top: containerMargins.top + coordinates.row * groupInfo.cellHeight,
                        left: containerMargins[leftStr] + coordinates.column * groupInfo.cellWidth,
                        height: coordinates.contentHeight,
                        width: coordinates.contentWidth
                    };

                    return itemPosition;
                },

                _layoutItem: function CellSpanningGroup_layoutItem(index) {
                    var entry = this.getItemSize(index);
                    this._items[index].style.cssText +=
                        ";-ms-grid-row:" + (entry.row + 1) +
                        ";-ms-grid-column:" + (entry.column + 1) +
                        ";-ms-grid-row-span:" + entry.rows +
                        ";-ms-grid-column-span:" + entry.columns +
                        ";height:" + entry.contentHeight +
                        "px;width:" + entry.contentWidth + "px";
                    _ElementUtilities.addClass(this._items[index], _Constants._laidOutClass);

                    return this._items[index];
                },

                _cleanContainers: function CellSpanningGroup_cleanContainers() {
                    var items = this._items,
                        len = items.length,
                        i;
                    for (i = 0; i < len; i++) {
                        items[i].style.cssText = "";
                        _ElementUtilities.removeClass(items[i], _Constants._laidOutClass);
                    }
                },

                // Occupancy map

                getColumnCount: function CellSpanningGroup_getColumnCount() {
                    return Math.ceil(this.occupancyMap.length / this._slotsPerColumn);
                },

                getOccupancyMapItemCount: function CellSpanningGroup_getOccupancyMapItemCount() {
                    var index = -1;

                    // Use forEach as the map may be sparse
                    this.occupancyMap.forEach(function (item) {
                        if (item.index > index) {
                            index = item.index;
                        }
                    });

                    return index + 1;
                },

                coordinateToIndex: function CellSpanningGroup_coordinateToIndex(c, r) {
                    return c * this._slotsPerColumn + r;
                },

                markSlotAsFull: function CellSpanningGroup_markSlotAsFull(index, itemEntry) {
                    var coordinates = this._layout._indexToCoordinate(index, this._slotsPerColumn),
                        toRow = coordinates.row + itemEntry.rows;
                    for (var r = coordinates.row; r < toRow && r < this._slotsPerColumn; r++) {
                        for (var c = coordinates.column, toColumn = coordinates.column + itemEntry.columns; c < toColumn; c++) {
                            this.occupancyMap[this.coordinateToIndex(c, r)] = itemEntry;
                        }
                    }
                    this._offScreenSlotsPerColumn = Math.max(this._offScreenSlotsPerColumn, toRow - this._slotsPerColumn);
                },

                isSlotEmpty: function CellSpanningGroup_isSlotEmpty(itemSize, row, column) {
                    for (var r = row, toRow = row + itemSize.rows; r < toRow; r++) {
                        for (var c = column, toColumn = column + itemSize.columns; c < toColumn; c++) {
                            if ((r >= this._slotsPerColumn) || (this.occupancyMap[this.coordinateToIndex(c, r)] !== undefined)) {
                                return false;
                            }
                        }
                    }
                    return true;
                },

                findEmptySlot: function CellSpanningGroup_findEmptySlot(startIndex, itemSize, newColumn) {
                    var coordinates = this._layout._indexToCoordinate(startIndex, this._slotsPerColumn),
                        startRow = coordinates.row,
                        lastColumn = Math.floor((this.occupancyMap.length - 1) / this._slotsPerColumn);

                    if (newColumn) {
                        for (var c = coordinates.column + 1; c <= lastColumn; c++) {
                            if (this.isSlotEmpty(itemSize, 0, c)) {
                                return this.coordinateToIndex(c, 0);
                            }
                        }
                    } else {
                        for (var c = coordinates.column; c <= lastColumn; c++) {
                            for (var r = startRow; r < this._slotsPerColumn; r++) {
                                if (this.isSlotEmpty(itemSize, r, c)) {
                                    return this.coordinateToIndex(c, r);
                                }
                            }
                            startRow = 0;
                        }
                    }

                    return (lastColumn + 1) * this._slotsPerColumn;
                },

                findItem: function CellSpanningGroup_findItem(index) {
                    for (var inMapIndex = index, len = this.occupancyMap.length; inMapIndex < len; inMapIndex++) {
                        var entry = this.occupancyMap[inMapIndex];
                        if (entry && entry.index === index) {
                            return inMapIndex;
                        }
                    }
                    return inMapIndex;
                },

                getItemSize: function CellSpanningGroup_getItemSize(index) {
                    var inMapIndex = this.findItem(index),
                        entry = this.occupancyMap[inMapIndex],
                        coords = this._layout._indexToCoordinate(inMapIndex, this._slotsPerColumn);

                    if (index === entry.index) {
                        return {
                            row: coords.row,
                            column: coords.column,
                            contentWidth: entry.contentWidth,
                            contentHeight: entry.contentHeight,
                            columns: entry.columns,
                            rows: entry.rows
                        };
                    } else {
                        return null;
                    }
                },

                resetMap: function CellSpanningGroup_resetMap() {
                    this.occupancyMap = [];
                    this.lastAdded = 0;
                    this._offScreenSlotsPerColumn = 0;
                },

                addItemToMap: function CellSpanningGroup_addItemToMap(index, itemInfo) {
                    var that = this;

                    function add(mapEntry, newColumn) {
                        var inMapIndex = that.findEmptySlot(that.lastAdded, mapEntry, newColumn);
                        that.lastAdded = inMapIndex;
                        that.markSlotAsFull(inMapIndex, mapEntry);
                    }

                    var groupInfo = that.groupInfo,
                        margins = that._layout._sizes.containerMargins,
                        mapEntry = {
                            index: index,
                            contentWidth: itemInfo.width,
                            contentHeight: itemInfo.height,
                            columns: Math.max(1, Math.ceil((itemInfo.width + margins.left + margins.right) / groupInfo.cellWidth)),
                            rows: Math.max(1, Math.ceil((itemInfo.height + margins.top + margins.bottom) / groupInfo.cellHeight))
                        };

                    add(mapEntry, itemInfo.newColumn);
                },

                indexFromOffset: function CellSpanningGroup_indexFromOffset(adjustedOffset, wholeItem, last) {
                    var measuredWidth = 0,
                        lastItem = 0,
                        groupInfo = this.groupInfo,
                        index = 0;

                    if (this.occupancyMap.length > 0) {
                        lastItem = this.getOccupancyMapItemCount() - 1;
                        measuredWidth = Math.ceil((this.occupancyMap.length - 1) / this._slotsPerColumn) * groupInfo.cellWidth;

                        if (adjustedOffset < measuredWidth) {
                            var counter = this._slotsPerColumn,
                                index = (Math.max(0, Math.floor(adjustedOffset / groupInfo.cellWidth)) + last) * this._slotsPerColumn - last;
                            while (!this.occupancyMap[index] && counter-- > 0) {
                                index += (last > 0 ? -1 : 1);
                            }
                            return {
                                index: index,
                                item: this.occupancyMap[index].index
                            };
                        } else {
                            index = this.occupancyMap.length - 1;
                        }
                    }

                    return {
                        index: index,
                        item: lastItem + (Math.max(0, Math.floor((adjustedOffset - measuredWidth) / groupInfo.cellWidth)) + last) * this._slotsPerColumn - last
                    };
                }
            });
        })

    });

    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {

        ListLayout: _Base.Namespace._lazy(function () {
            return _Base.Class.derive(exports._LegacyLayout, function ListLayout_ctor(options) {
                /// <signature helpKeyword="WinJS.UI.ListLayout">
                /// <summary locid="WinJS.UI.ListLayout">
                /// Creates a new ListLayout object.
                /// </summary>
                /// <param name="options" type="Object" locid="WinJS.UI.ListLayout_p:options">
                /// An object that contains one or more property/value pairs to apply to the new control. Each property of the options
                /// object corresponds to one of the object's properties or events. Event names must begin with "on".
                /// </param>
                /// <returns type="WinJS.UI.ListLayout" locid="WinJS.UI.ListLayout_returnValue">
                /// The new ListLayout object.
                /// </returns>
                /// </signature>
                options = options || {};
                this._itemInfo = {};
                this._groupInfo = {};
                this._groupHeaderPosition = options.groupHeaderPosition || HeaderPosition.top;
                this._inListMode = true;
                this.orientation = options.orientation || "vertical";
            }, {
                initialize: function ListLayout_initialize(site, groupsEnabled) {
                    _ElementUtilities.addClass(site.surface, _Constants._listLayoutClass);
                    exports._LegacyLayout.prototype.initialize.call(this, site, groupsEnabled);
                },

                uninitialize: function ListLayout_uninitialize() {
                    if (this._site) {
                        _ElementUtilities.removeClass(this._site.surface, _Constants._listLayoutClass);
                    }
                    exports._LegacyLayout.prototype.uninitialize.call(this);
                },

                layout: function ListLayout_layout(tree, changedRange, modifiedItems, modifiedGroups) {
                    if (!this._groupsEnabled && !this._horizontal) {
                        return this._layoutNonGroupedVerticalList(tree, changedRange, modifiedItems, modifiedGroups);
                    } else {
                        return exports._LegacyLayout.prototype.layout.call(this, tree, changedRange, modifiedItems, modifiedGroups);
                    }
                },

                _layoutNonGroupedVerticalList: function ListLayout_layoutNonGroupedVerticalList(tree, changedRange, modifiedItems, modifiedGroups) {
                    var that = this;
                    var perfId = "Layout:_layoutNonGroupedVerticalList";
                    that._site._writeProfilerMark(perfId + ",StartTM");
                    this._layoutPromise = that._measureItem(0).then(function () {
                        _ElementUtilities[that._usingStructuralNodes ? "addClass" : "removeClass"](that._site.surface, _Constants._structuralNodesClass);

                        if (that._sizes.viewportContentSize !== that._getViewportCrossSize()) {
                            that._viewportSizeChanged(that._getViewportCrossSize());
                        }

                        that._cacheRemovedElements(modifiedItems, that._cachedItemRecords, that._cachedInsertedItemRecords, that._cachedRemovedItems, false);
                        that._cacheRemovedElements(modifiedGroups, that._cachedHeaderRecords, that._cachedInsertedHeaderRecords, that._cachedRemovedHeaders, true);

                        var itemsContainer = tree[0].itemsContainer,
                            group = new Groups.UniformFlowGroup(that, itemsContainer);
                        that._groups = [group];
                        group.groupInfo = { enableCellSpanning: false };
                        group.startIndex = 0;
                        group.count = getItemsContainerLength(itemsContainer);
                        group.offset = 0;
                        group.layout();

                        that._site._writeProfilerMark(perfId + ":setSurfaceWidth,info");
                        that._site.surface.style.width = that._sizes.surfaceContentSize + "px";

                        that._layoutAnimations(modifiedItems, modifiedGroups);
                        that._site._writeProfilerMark(perfId + ":complete,info");
                        that._site._writeProfilerMark(perfId + ",StopTM");
                    }, function (error) {
                        that._site._writeProfilerMark(perfId + ":canceled,info");
                        that._site._writeProfilerMark(perfId + ",StopTM");
                        return Promise.wrapError(error);
                    });
                    return {
                        realizedRangeComplete: this._layoutPromise,
                        layoutComplete: this._layoutPromise
                    };
                },

                numberOfItemsPerItemsBlock: {
                    get: function ListLayout_getNumberOfItemsPerItemsBlock() {
                        var that = this;
                        // Measure when numberOfItemsPerItemsBlock is called so that we measure before ListView has created the full tree structure
                        // which reduces the trident layout required by measure.
                        return this._measureItem(0).then(function () {
                            if (that._envInfo.nestedFlexTooLarge || that._envInfo.nestedFlexTooSmall) {
                                that._usingStructuralNodes = false;
                            } else {
                                that._usingStructuralNodes = exports.ListLayout._numberOfItemsPerItemsBlock > 0;
                            }
                            return (that._usingStructuralNodes ? exports.ListLayout._numberOfItemsPerItemsBlock : null);
                        });
                    }
                },
            }, {
                // The maximum number of win-containers to put into each items block. win-containers
                // are grouped into items blocks in order to mitigate the costs of the platform doing
                // a layout in response to insertions and removals of win-containers.
                _numberOfItemsPerItemsBlock: 10
            });
        }),

        CellSpanningLayout: _Base.Namespace._lazy(function () {
            return _Base.Class.derive(exports._LayoutCommon, function CellSpanningLayout_ctor(options) {
                /// <signature helpKeyword="WinJS.UI.CellSpanningLayout">
                /// <summary locid="WinJS.UI.CellSpanningLayout">
                /// Creates a new CellSpanningLayout object.
                /// </summary>
                /// <param name="options" type="Object" locid="WinJS.UI.CellSpanningLayout_p:options">
                /// An object that contains one or more property/value pairs to apply to the new object. Each property of the options
                /// object corresponds to one of the object's properties or events. Event names must begin with "on".
                /// </param>
                /// <returns type="WinJS.UI.CellSpanningLayout" locid="WinJS.UI.CellSpanningLayout_returnValue">
                /// The new CellSpanningLayout object.
                /// </returns>
                /// </signature>
                options = options || {};
                this._itemInfo = options.itemInfo;
                this._groupInfo = options.groupInfo;
                this._groupHeaderPosition = options.groupHeaderPosition || HeaderPosition.top;
                this._horizontal = true;
                this._cellSpanning = true;
            }, {

                /// <field type="Number" integer="true" locid="WinJS.UI.CellSpanningLayout.maximumRowsOrColumns" helpKeyword="WinJS.UI.CellSpanningLayout.maximumRowsOrColumns">
                /// Gets or set the maximum number of rows or columns, depending on the orientation, to display before content begins to wrap.
                /// A value of 0 indicates that there is no maximum.
                /// </field>
                maximumRowsOrColumns: {
                    get: function () {
                        return this._maxRowsOrColumns;
                    },
                    set: function (value) {
                        this._setMaxRowsOrColumns(value);
                    }
                },

                /// <field type="Function" locid="WinJS.UI.CellSpanningLayout.itemInfo" helpKeyword="WinJS.UI.CellSpanningLayout.itemInfo">
                /// Gets or sets a function that returns the width and height of an item, as well as whether
                /// it should  appear in a new column. Setting this function improves performance because
                /// the ListView can allocate space for an item without having to measure it first.
                /// The function takes a single parameter: the index of the item to render.
                /// The function returns an object that has three properties:
                /// width: The  total width of the item.
                /// height: The total height of the item.
                /// newColumn: Set to true to create a column break; otherwise, false.
                /// </field>
                itemInfo: {
                    enumerable: true,
                    get: function () {
                        return this._itemInfo;
                    },
                    set: function (itemInfo) {
                        this._itemInfo = itemInfo;
                        this._invalidateLayout();
                    }
                },

                /// <field type="Function" locid="WinJS.UI.CellSpanningLayout.groupInfo" helpKeyword="WinJS.UI.CellSpanningLayout.groupInfo">
                /// Gets or sets a function that enables cell-spanning and establishes base cell dimensions.
                /// The function returns an object that has these properties:
                /// enableCellSpanning: Set to true to allow the ListView to contain items of multiple sizes.
                /// cellWidth: The width of the base cell.
                /// cellHeight: The height of the base cell.
                /// </field>
                groupInfo: {
                    enumerable: true,
                    get: function () {
                        return this._groupInfo;
                    },
                    set: function (groupInfo) {
                        this._groupInfo = groupInfo;
                        this._invalidateLayout();
                    }
                },

                /// <field type="String" oamOptionsDatatype="WinJS.UI.Orientation" locid="WinJS.UI.CellSpanningLayout.orientation" helpKeyword="WinJS.UI.CellSpanningLayout.orientation">
                /// Gets the orientation of the layout. CellSpanning layout only supports horizontal orientation.
                /// </field>
                orientation: {
                    enumerable: true,
                    get: function () {
                        return "horizontal";
                    }
                }
            });
        }),

        _LayoutWrapper: _Base.Namespace._lazy(function () {
            return _Base.Class.define(function LayoutWrapper_ctor(layout) {
                this.defaultAnimations = true;

                // Initialize and hitTest are required
                this.initialize = function LayoutWrapper_initialize(site, groupsEnabled) {
                    layout.initialize(site, groupsEnabled);
                };
                this.hitTest = function LayoutWrapper_hitTest(x, y) {
                    return layout.hitTest(x, y);
                };

                // These methods are optional
                layout.uninitialize && (this.uninitialize = function LayoutWrapper_uninitialize() {
                    layout.uninitialize();
                });

                if ("numberOfItemsPerItemsBlock" in layout) {
                    Object.defineProperty(this, "numberOfItemsPerItemsBlock", {
                        get: function LayoutWrapper_getNumberOfItemsPerItemsBlock() {
                            return layout.numberOfItemsPerItemsBlock;
                        }
                    });
                }

                layout._getItemPosition && (this._getItemPosition = function LayoutWrapper_getItemPosition(index) {
                    return layout._getItemPosition(index);
                });

                layout.itemsFromRange && (this.itemsFromRange = function LayoutWrapper_itemsFromRange(start, end) {
                    return layout.itemsFromRange(start, end);
                });

                layout.getAdjacent && (this.getAdjacent = function LayoutWrapper_getAdjacent(currentItem, pressedKey) {
                    return layout.getAdjacent(currentItem, pressedKey);
                });

                layout.dragOver && (this.dragOver = function LayoutWrapper_dragOver(x, y, dragInfo) {
                    return layout.dragOver(x, y, dragInfo);
                });

                layout.dragLeave && (this.dragLeave = function LayoutWrapper_dragLeave() {
                    return layout.dragLeave();
                });
                var propertyDefinition = {
                    enumerable: true,
                    get: function () {
                        return "vertical";
                    }
                };
                if (layout.orientation !== undefined) {
                    propertyDefinition.get = function () {
                        return layout.orientation;
                    };
                    propertyDefinition.set = function (value) {
                        layout.orientation = value;
                    };
                }
                Object.defineProperty(this, "orientation", propertyDefinition);

                if (layout.setupAnimations || layout.executeAnimations) {
                    this.defaultAnimations = false;
                    this.setupAnimations = function LayoutWrapper_setupAnimations() {
                        return layout.setupAnimations();
                    };
                    this.executeAnimations = function LayoutWrapper_executeAnimations() {
                        return layout.executeAnimations();
                    };
                }

                if (layout.layout) {
                    if (this.defaultAnimations) {
                        var that = this;
                        this.layout = function LayoutWrapper_layout(tree, changedRange, modifiedItems, modifiedGroups) {
                            var promises = normalizeLayoutPromises(layout.layout(tree, changedRange, [], [])),
                                synchronous;
                            promises.realizedRangeComplete.then(function () {
                                synchronous = true;
                            });
                            synchronous && that._layoutAnimations(modifiedItems, modifiedGroups);
                            return promises;
                        };
                    } else {
                        this.layout = function LayoutWrapper_layout(tree, changedRange, modifiedItems, modifiedGroups) {
                            return normalizeLayoutPromises(layout.layout(tree, changedRange, modifiedItems, modifiedGroups));
                        };
                    }
                }
            }, {
                uninitialize: function LayoutWrapper_uninitialize() {
                },
                numberOfItemsPerItemsBlock: {
                    get: function LayoutWrapper_getNumberOfItemsPerItemsBlock() {
                    }
                },
                layout: function LayoutWrapper_layout(tree, changedRange, modifiedItems, modifiedGroups) {
                    if (this.defaultAnimations) {
                        this._layoutAnimations(modifiedItems, modifiedGroups);
                    }
                    return normalizeLayoutPromises();
                },
                itemsFromRange: function LayoutWrapper_itemsFromRange() {
                    return { firstIndex: 0, lastIndex: Number.MAX_VALUE };
                },
                getAdjacent: function LayoutWrapper_getAdjacent(currentItem, pressedKey) {

                    switch (pressedKey) {
                        case Key.pageUp:
                        case Key.upArrow:
                        case Key.leftArrow:
                            return { type: currentItem.type, index: currentItem.index - 1 };
                        case Key.downArrow:
                        case Key.rightArrow:
                        case Key.pageDown:
                            return { type: currentItem.type, index: currentItem.index + 1 };
                    }
                },
                dragOver: function LayoutWrapper_dragOver() {
                },
                dragLeave: function LayoutWrapper_dragLeaver() {
                },
                setupAnimations: function LayoutWrapper_setupAnimations() {
                },
                executeAnimations: function LayoutWrapper_executeAnimations() {
                },
                _getItemPosition: function LayoutWrapper_getItemPosition() {
                },
                _layoutAnimations: function LayoutWrapper_layoutAnimations() {
                },
            });
        }),
    });

    function normalizeLayoutPromises(retVal) {
        if (Promise.is(retVal)) {
            return {
                realizedRangeComplete: retVal,
                layoutComplete: retVal
            };
        } else if (typeof retVal === "object" && retVal && retVal.layoutComplete) {
            return retVal;
        } else {
            return {
                realizedRangeComplete: Promise.wrap(),
                layoutComplete: Promise.wrap()
            };
        }
    }

    var HeaderPosition = {
        left: "left",
        top: "top"
    };

    function getMargins(element) {
        return {
            left: getDimension(element, "marginLeft"),
            right: getDimension(element, "marginRight"),
            top: getDimension(element, "marginTop"),
            bottom: getDimension(element, "marginBottom")
        };
    }

    // Layout, _LayoutCommon, and _LegacyLayout are defined ealier so that their fully
    // qualified names can be used in _Base.Class.derive. This is required by Blend.
    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {
        HeaderPosition: HeaderPosition,
        _getMargins: getMargins
    });
});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/ListView/_VirtualizeContentsView',[
    'exports',
    '../../Core/_Global',
    '../../Core/_Base',
    '../../Core/_BaseUtils',
    '../../Promise',
    '../../_Signal',
    '../../Scheduler',
    '../../Utilities/_Dispose',
    '../../Utilities/_ElementUtilities',
    '../../Utilities/_SafeHtml',
    '../../Utilities/_UI',
    '../ItemContainer/_Constants',
    '../ItemContainer/_ItemEventsHandler',
    './_Helpers',
    './_ItemsContainer'
    ], function virtualizeContentsViewInit(exports, _Global, _Base, _BaseUtils, Promise, _Signal, Scheduler, _Dispose, _ElementUtilities, _SafeHtml, _UI, _Constants, _ItemEventsHandler, _Helpers, _ItemsContainer) {
    "use strict";

    function setFlow(from, to) {
        _ElementUtilities._setAttribute(from, "aria-flowto", to.id);
        _ElementUtilities._setAttribute(to, "x-ms-aria-flowfrom", from.id);
    }

    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {
        _VirtualizeContentsView: _Base.Namespace._lazy(function () {

            function cooperativeQueueWorker(info) {
                var workItems = info.job._workItems;
                var work;
                while (workItems.length && !info.shouldYield) {
                    work = workItems.shift();
                    work();
                }

                info.setWork(cooperativeQueueWorker);

                if (!workItems.length) {
                    info.job.pause();
                }
            }

            function scheduleQueueJob(priority, name) {

                var job = Scheduler.schedule(cooperativeQueueWorker, priority, null, name);

                job._workItems = [];

                job.addWork = function (work, head) {
                    if (head) {
                        this._workItems.unshift(work);
                    } else {
                        this._workItems.push(work);
                    }
                    this.resume();
                };

                job.clearWork = function () {
                    this._workItems.length = 0;
                };

                job.dispose = function () {
                    this.cancel();
                    this._workItems.length = 0;
                };

                return job;
            }

            function shouldWaitForSeZo(listView) {
                return listView._zooming || listView._pinching;
            }

            function waitForSeZo(listView, timeout) {
                // waitForSeZo will block until sezo calls endZoom on listview, or a timeout duration has elapsed to
                // unblock a potential deadlock between the sezo waiting on container creation, and container creation
                // waiting on endZoom.

                if (listView._isZombie()) { return Promise.wrap(); }
                if (shouldWaitForSeZo(listView)) {
                    if (+timeout !== timeout) {
                        timeout = _VirtualizeContentsView._waitForSeZoTimeoutDuration;
                    }
                    //To improve SeZo's zoom animation and pinch detection perf, we want to ensure unimportant task
                    //is only run while zooming or pinching is not in progress.
                    return Promise.timeout(_VirtualizeContentsView._waitForSeZoIntervalDuration).then(function () {
                        timeout -= _VirtualizeContentsView._waitForSeZoIntervalDuration;
                        if (timeout <= 0) {
                            return true;
                        }
                        return waitForSeZo(listView, timeout);
                    });
                } else {
                    return Promise.wrap();
                }
            }

            function makeFunctor(scrollToFunctor) {
                if (typeof scrollToFunctor === "number") {
                    var pos = scrollToFunctor;

                    scrollToFunctor = function () {
                        return {
                            position: pos,
                            direction: "right"
                        };
                    };
                }
                return scrollToFunctor;
            }

            var _VirtualizeContentsView = _Base.Class.define(function VirtualizeContentsView_ctor(listView) {
                this._listView = listView;
                this._forceRelayout = false;
                this.items = new _ItemsContainer._ItemsContainer(listView);
                this.firstIndexDisplayed = -1;
                this.lastIndexDisplayed = -1;
                this.begin = 0;
                this.end = 0;
                this._realizePass = 1;
                this._firstLayoutPass = true;
                this._runningAnimations = null;
                this._renderCompletePromise = Promise.wrap();
                this._state = new CreatedState(this);
                this._createLayoutSignal();
                this._createTreeBuildingSignal();
                this._layoutWork = null;
                this._onscreenJob = scheduleQueueJob(Scheduler.Priority.aboveNormal, "on-screen items");
                this._frontOffscreenJob = scheduleQueueJob(Scheduler.Priority.normal, "front off-screen items");
                this._backOffscreenJob = scheduleQueueJob(Scheduler.Priority.belowNormal, "back off-screen items");
                this._scrollbarPos = 0;
                this._direction = "right";
                this._scrollToFunctor = makeFunctor(0);
            },
            {

                _dispose: function VirtualizeContentsView_dispose() {
                    this.cleanUp();
                    this.items = null;
                    this._renderCompletePromise && this._renderCompletePromise.cancel();
                    this._renderCompletePromise = null;
                    this._onscreenJob.dispose();
                    this._frontOffscreenJob.dispose();
                    this._backOffscreenJob.dispose();
                },

                _createItem: function VirtualizeContentsView_createItem(itemIndex, itemPromise, available, unavailable) {
                    this._listView._writeProfilerMark("createItem(" + itemIndex + ") " + this._getBoundingRectString(itemIndex) + ",info");

                    var that = this;
                    that._listView._itemsManager._itemFromItemPromiseThrottled(itemPromise).done(
                        function (element) {
                            if (element) {
                                available(itemIndex, element, that._listView._itemsManager._recordFromElement(element));
                            } else {
                                unavailable(itemIndex);
                            }
                        },
                        function (err) {
                            unavailable(itemIndex);
                            return Promise.wrapError(err);
                        }
                    );
                },

                _addItem: function VirtualizeContentsView_addItem(fragment, itemIndex, element, currentPass) {
                    if (this._realizePass === currentPass) {
                        var record = this._listView._itemsManager._recordFromElement(element);

                        delete this._pendingItemPromises[record.itemPromise.handle];

                        this.items.setItemAt(itemIndex, {
                            itemBox: null,
                            container: null,
                            element: element,
                            detached: true,
                            itemsManagerRecord: record
                        });
                    }
                },

                finalItem: function VirtualizeContentsView_finalItem() {
                    return this.containers ? Promise.wrap(this.containers.length - 1) : Promise.cancel;
                },

                _setSkipRealizationForChange: function (skip) {
                    if (skip) {
                        if (this._realizationLevel !== _VirtualizeContentsView._realizationLevel.realize) {
                            this._realizationLevel = _VirtualizeContentsView._realizationLevel.skip;
                        }
                    } else {
                        this._realizationLevel = _VirtualizeContentsView._realizationLevel.realize;
                    }
                },

                _realizeItems: function VirtualizeContentsView_realizeItems(fragment, begin, end, count, currentPass, scrollbarPos, direction, firstInView, lastInView, ignoreGaps) {
                    var perfId = "_realizeItems(" + begin + "-" + (end - 1) + ") visible(" + firstInView + "-" + lastInView + ")";

                    this._listView._writeProfilerMark(perfId + ",StartTM");

                    direction = direction || "right";

                    var counter = end - begin;
                    var inView = lastInView - firstInView + 1,
                        inViewCounter = inView,
                        rightOffscreenCount = end - lastInView - 1,
                        leftOffscreenCount = firstInView - begin;
                    var renderCompletePromises = [];
                    var entranceAnimationSignal = new _Signal();
                    var viewportItemsRealized = new _Signal();
                    var frontItemsRealized = new _Signal();

                    var that = this;

                    function itemIsReady(itemIndex, itemsManagerRecord) {
                        renderCompletePromises.push(Promise._cancelBlocker(itemsManagerRecord.renderComplete));

                        delivered(itemIndex);
                    }

                    function appendItemsToDom(startIndex, endIndex) {
                        that._listView._writeProfilerMark("_realizeItems_appendedItemsToDom,StartTM");
                        if (that._listView._isZombie()) { return; }

                        function updateSwipeable(itemData, element) {
                            if (!itemData.updatedSwipeableAttribute && (that._listView.itemsDraggable || that._listView.itemsReorderable || that._listView._swipeable)) {
                                itemData.itemsManagerRecord.renderComplete.done(function () {
                                    if (that._realizePass === currentPass) {
                                        var dragDisabledOnItem = _ElementUtilities.hasClass(element, _Constants._nonDraggableClass),
                                            selectionDisabledOnItem = _ElementUtilities.hasClass(element, _Constants._nonSelectableClass),
                                            dragEnabled = (that._listView.itemsDraggable || that._listView.itemsReorderable),
                                            swipeSelectEnabled = (that._listView._selectionAllowed() && that._listView._swipeBehavior === _UI.SwipeBehavior.select);
                                        if (dragEnabled && !dragDisabledOnItem) {
                                            itemData.itemBox.draggable = true;
                                        }

                                        if (that._listView._swipeable && ((dragEnabled && !swipeSelectEnabled && dragDisabledOnItem) ||
                                            (swipeSelectEnabled && !dragEnabled && selectionDisabledOnItem) ||
                                            (dragDisabledOnItem && selectionDisabledOnItem))) {
                                            _ElementUtilities.addClass(itemData.itemBox, _Constants._nonSwipeableClass);
                                        }
                                        itemData.updatedSwipeableAttribute = true;
                                    }
                                });
                            }
                        }

                        var itemIndex;
                        var appendItemsCount = 0;
                        var firstIndex = -1;
                        var lastIndex = -1;
                        for (itemIndex = startIndex; itemIndex <= endIndex; itemIndex++) {
                            var itemData = that.items.itemDataAt(itemIndex);
                            if (itemData) {
                                var element = itemData.element,
                                    itemBox = itemData.itemBox;

                                if (!itemBox) {
                                    itemBox = that._listView._itemBoxTemplate.cloneNode(true);
                                    itemData.itemBox = itemBox;

                                    itemBox.appendChild(element);
                                    _ElementUtilities.addClass(element, _Constants._itemClass);

                                    that._listView._setupAriaSelectionObserver(element);

                                    if (that._listView._isSelected(itemIndex)) {
                                        _ItemEventsHandler._ItemEventsHandler.renderSelection(itemBox, element, true, true);
                                    }

                                    that._listView._currentMode().renderDragSourceOnRealizedItem(itemIndex, itemBox);
                                }

                                updateSwipeable(itemData, element, itemBox);

                                var container = that.getContainer(itemIndex);
                                if (itemBox.parentNode !== container) {
                                    that._appendAndRestoreFocus(container, itemBox);

                                    appendItemsCount++;
                                    if (firstIndex < 0) {
                                        firstIndex = itemIndex;
                                    }
                                    lastIndex = itemIndex;

                                    itemData.container = container;
                                    if (that._listView._isSelected(itemIndex)) {
                                        _ElementUtilities.addClass(container, _Constants._selectedClass);
                                    }

                                    _ElementUtilities.removeClass(container, _Constants._backdropClass);

                                    // elementAvailable needs to be called after fragment.appendChild. elementAvailable fulfills a promise for items requested
                                    // by the keyboard focus handler. That handler will explicitly call .focus() on the element, so in order for
                                    // the focus handler to work, the element needs to be in a tree prior to focusing.

                                    that.items.elementAvailable(itemIndex);
                                }
                            }
                        }

                        that._listView._writeProfilerMark("_realizeItems_appendedItemsToDom,StopTM");
                        if (appendItemsCount > 0) {
                            that._listView._writeProfilerMark("_realizeItems_appendedItemsToDom:" + appendItemsCount + " (" + firstIndex + "-" + lastIndex + "),info");
                            that._reportElementsLevel(direction);
                        }
                    }

                    function removeGaps(first, last, begin, end) {
                        if (ignoreGaps) {
                            return;
                        }
                        // If we realized items 0 through 20 and then scrolled so that items 25 - 30 are on screen when we
                        // append them to the dom we should remove items 0 - 20 from the dom so there are no gaps between the
                        // two realized spots.

                        // Walk backwards from the beginning and if we find an item which is missing remove the rest
                        var foundMissing = false;
                        while (first >= begin) {
                            foundMissing = testGap(first, foundMissing);
                            first--;
                        }

                        // Walk forwards from the end and if we find an item which is missing remove the rest
                        foundMissing = false;
                        while (last <= end) {
                            foundMissing = testGap(last, foundMissing);
                            last++;
                        }

                        function testGap(itemIndex, foundMissing) {
                            // This helper method is called for each index and once an item is missing from the dom
                            // it removes any future one it encounters.
                            var itemData = that.items.itemDataAt(itemIndex);
                            if (itemData) {
                                var itemBox = itemData.itemBox;
                                if (!itemBox || !itemBox.parentNode) {
                                    return true;
                                } else if (foundMissing) {
                                    _ElementUtilities.addClass(itemBox.parentNode, _Constants._backdropClass);
                                    itemBox.parentNode.removeChild(itemBox);
                                    return true;
                                } else {
                                    return false;
                                }
                            } else {
                                return true;
                            }
                        }
                    }

                    function scheduleReadySignal(first, last, job, dir, head) {
                        var promises = [];

                        for (var i = first; i <= last; i++) {
                            var itemData = that.items.itemDataAt(i);
                            if (itemData) {
                                promises.push(itemData.itemsManagerRecord.itemPromise);
                            }
                        }

                        function schedule(itemIndex) {
                            var itemData = that.items.itemDataAt(itemIndex);
                            if (itemData) {
                                var record = itemData.itemsManagerRecord;
                                if (!record.readyComplete && that._realizePass === currentPass) {
                                    job.addWork(function () {
                                        if (that._listView._isZombie()) {
                                            return;
                                        }
                                        if (record.pendingReady && that._realizePass === currentPass) {
                                            that._listView._writeProfilerMark("pendingReady(" + itemIndex + "),info");
                                            record.pendingReady();
                                        }
                                    }, head);
                                }
                            }
                        }

                        Promise.join(promises).then(function () {
                            if (dir === "right") {
                                for (var i = first; i <= last; i++) {
                                    schedule(i);
                                }
                            } else {
                                for (var i = last; i >= first; i--) {
                                    schedule(i);
                                }
                            }
                        });
                    }

                    function delivered(index) {
                        if (that._realizePass !== currentPass) {
                            return;
                        }

                        if (index >= firstInView && index <= lastInView) {
                            if (--inViewCounter === 0) {
                                appendItemsToDom(firstInView, lastInView);
                                removeGaps(firstInView, lastInView, begin, end);

                                if (that._firstLayoutPass) {
                                    scheduleReadySignal(firstInView, lastInView, that._frontOffscreenJob, direction === "right" ? "left" : "right", true);

                                    var entranceAnimation = Scheduler.schedulePromiseHigh(null, "WinJS.UI.ListView.entranceAnimation").then(function () {
                                        if (that._listView._isZombie()) { return; }
                                        that._listView._writeProfilerMark("entranceAnimation,StartTM");
                                        var promise = that._listView._animateListEntrance(!that._firstEntranceAnimated);
                                        that._firstEntranceAnimated = true;
                                        return promise;
                                    });

                                    that._runningAnimations = Promise.join([that._runningAnimations, entranceAnimation]);
                                    that._runningAnimations.done(function () {
                                        that._listView._writeProfilerMark("entranceAnimation,StopTM");
                                        if (that._realizePass === currentPass) {
                                            that._runningAnimations = null;
                                            entranceAnimationSignal.complete();
                                        }
                                    });
                                    that._firstLayoutPass = false;

                                    if (that._listView._isCurrentZoomView) {
                                        Scheduler.requestDrain(that._onscreenJob.priority);
                                    }
                                } else {
                                    // during scrolling ready for onscreen items after front off screen items
                                    scheduleReadySignal(firstInView, lastInView, that._frontOffscreenJob, direction);
                                    entranceAnimationSignal.complete();
                                }

                                that._updateHeaders(that._listView._canvas, firstInView, lastInView + 1).done(function () {
                                    viewportItemsRealized.complete();
                                });
                            }
                        } else if (index < firstInView) {
                            --leftOffscreenCount;
                            if (leftOffscreenCount % inView === 0) {
                                appendItemsToDom(begin, firstInView - 1);
                            }
                            if (!leftOffscreenCount) {
                                that._updateHeaders(that._listView._canvas, begin, firstInView).done(function () {
                                    if (direction !== "right") {
                                        frontItemsRealized.complete();
                                    }
                                });
                                scheduleReadySignal(begin, firstInView - 1, direction !== "right" ? that._frontOffscreenJob : that._backOffscreenJob, "left");
                            }
                        } else if (index > lastInView) {
                            --rightOffscreenCount;
                            if (rightOffscreenCount % inView === 0) {
                                appendItemsToDom(lastInView + 1, end - 1);
                            }
                            if (!rightOffscreenCount) {
                                that._updateHeaders(that._listView._canvas, lastInView + 1, end).then(function () {
                                    if (direction === "right") {
                                        frontItemsRealized.complete();
                                    }
                                });
                                scheduleReadySignal(lastInView + 1, end - 1, direction === "right" ? that._frontOffscreenJob : that._backOffscreenJob, "right");
                            }
                        }
                        counter--;

                        if (counter === 0) {
                            that._renderCompletePromise = Promise.join(renderCompletePromises).then(null, function (e) {
                                var error = Array.isArray(e) && e.some(function (item) { return item && !(item instanceof Error && item.name === "Canceled"); });
                                if (error) {
                                    // rethrow
                                    return Promise.wrapError(e);
                                }
                            });

                            (that._headerRenderPromises || Promise.wrap()).done(function () {
                                Scheduler.schedule(function VirtualizeContentsView_async_delivered() {
                                    if (that._listView._isZombie()) {
                                        workCompleteSignal.cancel();
                                    } else {
                                        workCompleteSignal.complete();
                                    }
                                }, Math.min(that._onscreenJob.priority, that._backOffscreenJob.priority), null, "WinJS.UI.ListView._allItemsRealized");
                            });
                        }
                    }

                    function newItemIsReady(itemIndex, element, itemsManagerRecord) {
                        if (that._realizePass === currentPass) {
                            var element = itemsManagerRecord.element;
                            that._addItem(fragment, itemIndex, element, currentPass);
                            itemIsReady(itemIndex, itemsManagerRecord);
                        }
                    }

                    if (counter > 0) {
                        var createCount = 0;
                        var updateCount = 0;
                        var cleanCount = 0;
                        that.firstIndexDisplayed = firstInView;
                        that.lastIndexDisplayed = lastInView;

                        var isCurrentZoomView = that._listView._isCurrentZoomView;
                        if (that._highPriorityRealize && (that._firstLayoutPass || that._hasAnimationInViewportPending)) {
                            // startup or edits that will animate items in the viewport
                            that._highPriorityRealize = false;
                            that._onscreenJob.priority = Scheduler.Priority.high;
                            that._frontOffscreenJob.priority = Scheduler.Priority.normal;
                            that._backOffscreenJob.priority = Scheduler.Priority.belowNormal;
                        } else if (that._highPriorityRealize) {
                            // edits that won't animate items in the viewport
                            that._highPriorityRealize = false;
                            that._onscreenJob.priority = Scheduler.Priority.high;
                            that._frontOffscreenJob.priority = Scheduler.Priority.high - 1;
                            that._backOffscreenJob.priority = Scheduler.Priority.high - 1;
                        } else if (isCurrentZoomView) {
                            // scrolling
                            that._onscreenJob.priority = Scheduler.Priority.aboveNormal;
                            that._frontOffscreenJob.priority = Scheduler.Priority.normal;
                            that._backOffscreenJob.priority = Scheduler.Priority.belowNormal;
                        } else {
                            // hidden ListView in SeZo
                            that._onscreenJob.priority = Scheduler.Priority.belowNormal;
                            that._frontOffscreenJob.priority = Scheduler.Priority.idle;
                            that._backOffscreenJob.priority = Scheduler.Priority.idle;
                        }

                        // Create a promise to wrap the work in the queue. When the queue gets to the last item we can mark
                        // the work promise complete and if the work promise is canceled we cancel the queue.
                        //
                        var workCompleteSignal = new _Signal();

                        // If the version manager recieves a notification we clear the work in the work queues
                        //
                        var cancelToken = that._listView._versionManager.cancelOnNotification(workCompleteSignal.promise);

                        var queueStage1AfterStage0 = function (job, record) {
                            if (record.startStage1) {
                                record.stage0.then(function () {
                                    if (that._realizePass === currentPass && record.startStage1) {
                                        job.addWork(record.startStage1);
                                    }
                                });
                            }
                        };

                        var queueWork = function (job, itemIndex) {
                            var itemData = that.items.itemDataAt(itemIndex);
                            if (!itemData) {
                                var itemPromise = that._listView._itemsManager._itemPromiseAtIndex(itemIndex);

                                // Remember this pending item promise and avoid canceling it from the previous realization pass.
                                that._pendingItemPromises[itemPromise.handle] = itemPromise;
                                delete that._previousRealizationPendingItemPromises[itemPromise.handle];

                                job.addWork(function VirtualizeContentsView_realizeItemsWork() {
                                    if (that._listView._isZombie()) {
                                        return;
                                    }

                                    createCount++;
                                    that._createItem(itemIndex, itemPromise, newItemIsReady, delivered);

                                    // _createItem runs user code
                                    if (that._listView._isZombie() || that._realizePass !== currentPass) {
                                        return;
                                    }

                                    if (itemPromise.handle) {
                                        var record = that._listView._itemsManager._recordFromHandle(itemPromise.handle);
                                        queueStage1AfterStage0(job, record);
                                    }
                                });
                            }

                        };

                        var queueRight = function (job, first, last) {
                            for (var itemIndex = first; itemIndex <= last; itemIndex++) {
                                queueWork(job, itemIndex);
                            }
                        };

                        var queueLeft = function (job, first, last) {
                            // Always build the left side in the direction away from the center.
                            for (var itemIndex = last; itemIndex >= first; itemIndex--) {
                                queueWork(job, itemIndex);
                            }
                        };

                        var handleExistingRange = function (job, first, last) {
                            for (var itemIndex = first; itemIndex <= last; itemIndex++) {
                                var itemData = that.items.itemDataAt(itemIndex);
                                if (itemData) {
                                    var record = itemData.itemsManagerRecord;
                                    itemIsReady(itemIndex, record);
                                    updateCount++;
                                    queueStage1AfterStage0(job, record);
                                }
                            }
                        };

                        // PendingItemPromises are the item promises which we have requested from the ItemsManager
                        // which have not returned an element (placeholder or real). Since we only clean up items
                        // which have an element in _unrealizeItems we need to remember these item promises. We cancel
                        // the item promises from the previous realization iteration if those item promises are not
                        // used for the current realization.
                        this._previousRealizationPendingItemPromises = this._pendingItemPromises || {};
                        this._pendingItemPromises = {};

                        var emptyFront;
                        if (direction === "left") {
                            queueLeft(that._onscreenJob, firstInView, lastInView);
                            queueLeft(that._frontOffscreenJob, begin, firstInView - 1);
                            emptyFront = begin > (firstInView - 1);
                        } else {
                            queueRight(that._onscreenJob, firstInView, lastInView);
                            queueRight(that._frontOffscreenJob, lastInView + 1, end - 1);
                            emptyFront = lastInView + 1 > (end - 1);
                        }

                        // Anything left in _previousRealizationPendingItemPromises can be canceled here.
                        // Note: we are doing this synchronously. If we didn't do it synchronously we would have had to merge
                        // _previousRealizationPendingItemPromises and _pendingItemPromises together. This also has the great
                        // benefit to cancel item promises in the backOffScreenArea which are much less important.
                        for (var i = 0, handles = Object.keys(this._previousRealizationPendingItemPromises), len = handles.length; i < len; i++) {
                            var handle = handles[i];
                            that._listView._itemsManager.releaseItemPromise(this._previousRealizationPendingItemPromises[handle]);
                        }
                        this._previousRealizationPendingItemPromises = {};


                        // Handle existing items in the second pass to make sure that raising ready signal is added to the queues after creating items
                        handleExistingRange(that._onscreenJob, firstInView, lastInView);
                        if (direction === "left") {
                            handleExistingRange(that._frontOffscreenJob, begin, firstInView - 1);
                        } else {
                            handleExistingRange(that._frontOffscreenJob, lastInView + 1, end - 1);
                        }

                        var showProgress = (inViewCounter === lastInView - firstInView + 1);

                        if (that._firstLayoutPass) {
                            that._listView._canvas.style.opacity = 0;
                        } else {
                            if (showProgress) {
                                that._listView._showProgressBar(that._listView._element, "50%", "50%");
                            } else {
                                that._listView._hideProgressBar();
                            }
                        }

                        that._frontOffscreenJob.pause();
                        that._backOffscreenJob.pause();

                        viewportItemsRealized.promise.done(
                            function () {
                                that._frontOffscreenJob.resume();

                                if (emptyFront) {
                                    frontItemsRealized.complete();
                                }
                            },
                            function () {
                                workCompleteSignal.cancel();
                            }
                        );

                        frontItemsRealized.promise.done(function () {
                            that._listView._writeProfilerMark("frontItemsRealized,info");

                            if (direction === "left") {
                                queueRight(that._backOffscreenJob, lastInView + 1, end - 1);
                                handleExistingRange(that._backOffscreenJob, lastInView + 1, end - 1);
                            } else {
                                queueLeft(that._backOffscreenJob, begin, firstInView - 1);
                                handleExistingRange(that._backOffscreenJob, begin, firstInView - 1);
                            }

                            that._backOffscreenJob.resume();
                        });

                        workCompleteSignal.promise.done(
                            function () {
                                that._listView._versionManager.clearCancelOnNotification(cancelToken);

                                that._listView._writeProfilerMark(perfId + " complete(created:" + createCount + " updated:" + updateCount + "),info");
                            },
                            function (err) {
                                that._listView._versionManager.clearCancelOnNotification(cancelToken);
                                that._onscreenJob.clearWork();
                                that._frontOffscreenJob.clearWork();
                                that._backOffscreenJob.clearWork();

                                entranceAnimationSignal.cancel();
                                viewportItemsRealized.cancel();

                                that._listView._writeProfilerMark(perfId + " canceled(created:" + createCount + " updated:" + updateCount + " clean:" + cleanCount + "),info");
                                return Promise.wrapError(err);
                            }
                        );

                        that._listView._writeProfilerMark(perfId + ",StopTM");
                        return {
                            viewportItemsRealized: viewportItemsRealized.promise,
                            allItemsRealized: workCompleteSignal.promise,
                            loadingCompleted: Promise.join([workCompleteSignal.promise, entranceAnimationSignal.promise]).then(function () {
                                var promises = [];

                                for (var i = begin; i < end; i++) {
                                    var itemData = that.items.itemDataAt(i);
                                    if (itemData) {
                                        promises.push(itemData.itemsManagerRecord.itemReadyPromise);
                                    }
                                }
                                return Promise._cancelBlocker(Promise.join(promises));
                            })
                        };
                    } else {
                        that._listView._writeProfilerMark(perfId + ",StopTM");
                        return {
                            viewportItemsRealized: Promise.wrap(),
                            allItemsRealized: Promise.wrap(),
                            loadingCompleted: Promise.wrap()
                        };
                    }
                },

                _setAnimationInViewportState: function VirtualizeContentsView_setAnimationInViewportState(modifiedElements) {
                    this._hasAnimationInViewportPending = false;
                    if (modifiedElements && modifiedElements.length > 0) {
                        var viewportLength = this._listView._getViewportLength(),
                            range = this._listView._layout.itemsFromRange(this._scrollbarPos, this._scrollbarPos + viewportLength - 1);
                        for (var i = 0, len = modifiedElements.length; i < len; i++) {
                            var modifiedElement = modifiedElements[i];
                            if (modifiedElement.newIndex >= range.firstIndex && modifiedElement.newIndex <= range.lastIndex && modifiedElement.newIndex !== modifiedElement.oldIndex) {
                                this._hasAnimationInViewportPending = true;
                                break;
                            }
                        }
                    }
                },

                _addHeader: function VirtualizeContentsView_addHeader(fragment, groupIndex) {
                    var that = this;
                    return this._listView._groups.renderGroup(groupIndex).then(function (header) {
                        if (header) {
                            header.element.tabIndex = 0;
                            var placeholder = that._getHeaderContainer(groupIndex);
                            if (header.element.parentNode !== placeholder) {
                                placeholder.appendChild(header.element);
                                _ElementUtilities.addClass(header.element, _Constants._headerClass);
                            }

                            that._listView._groups.setDomElement(groupIndex, header.element);
                        }
                    });
                },

                _updateHeaders: function VirtualizeContentsView_updateHeaders(fragment, begin, end) {
                    var that = this;

                    function updateGroup(index) {
                        var group = that._listView._groups.group(index);
                        if (group && !group.header) {
                            var headerPromise = group.headerPromise;
                            if (!headerPromise) {
                                headerPromise = group.headerPromise = that._addHeader(fragment, index);
                                headerPromise.done(function () {
                                    group.headerPromise = null;
                                }, function () {
                                    group.headerPromise = null;
                                });
                            }
                            return headerPromise;
                        }
                        return Promise.wrap();
                    }

                    this._listView._groups.removeElements();

                    var groupStart = this._listView._groups.groupFromItem(begin),
                        groupIndex = groupStart,
                        groupEnd = this._listView._groups.groupFromItem(end - 1),
                        realizationPromises = [];

                    if (groupIndex !== null) {
                        for (; groupIndex <= groupEnd; groupIndex++) {
                            realizationPromises.push(updateGroup(groupIndex));
                        }
                    }

                    function done() {
                        that._headerRenderPromises = null;
                    }
                    this._headerRenderPromises = Promise.join(realizationPromises, this._headerRenderPromises).then(done, done);
                    return this._headerRenderPromises || Promise.wrap();
                },

                _unrealizeItem: function VirtualizeContentsView_unrealizeItem(itemIndex) {
                    var listView = this._listView,
                        focusedItemPurged;

                    this._listView._writeProfilerMark("_unrealizeItem(" + itemIndex + "),info");

                    var focused = listView._selection._getFocused();
                    if (focused.type !== _UI.ObjectType.groupHeader && focused.index === itemIndex) {
                        listView._unsetFocusOnItem();
                        focusedItemPurged = true;
                    }
                    var itemData = this.items.itemDataAt(itemIndex),
                        item = itemData.element,
                        itemBox = itemData.itemBox;

                    if (itemBox && itemBox.parentNode) {
                        _ElementUtilities.removeClass(itemBox.parentNode, _Constants._selectedClass);
                        _ElementUtilities.removeClass(itemBox.parentNode, _Constants._footprintClass);
                        _ElementUtilities.addClass(itemBox.parentNode, _Constants._backdropClass);
                        itemBox.parentNode.removeChild(itemBox);
                    }
                    itemData.container = null;

                    if (listView._currentMode().itemUnrealized) {
                        listView._currentMode().itemUnrealized(itemIndex, itemBox);
                    }

                    this.items.removeItem(itemIndex);

                    // If this wasn't released by the itemsManager already, then
                    // we remove it. This handles the special case of delete
                    // occuring on an item that is outside of the current view, but
                    // has not been cleaned up yet.
                    //
                    if (!itemData.removed) {
                        listView._itemsManager.releaseItem(item);
                    }


                    _Dispose._disposeElement(item);

                    if (focusedItemPurged) {
                        // If the focused item was purged, we'll still want to focus on it if it comes into view sometime in the future.
                        // calling _setFocusOnItem once the item is removed from this.items will set up a promise that will be fulfilled
                        // if the item ever gets reloaded
                        listView._setFocusOnItem(listView._selection._getFocused());
                    }
                },

                _unrealizeGroup: function VirtualizeContentsView_unrealizeGroup(group) {
                    var headerElement = group.header,
                        focusedItemPurged;

                    var focused = this._listView._selection._getFocused();
                    if (focused.type === _UI.ObjectType.groupHeader && this._listView._groups.group(focused.index) === group) {
                        this._listView._unsetFocusOnItem();
                        focusedItemPurged = true;
                    }

                    if (headerElement.parentNode) {
                        headerElement.parentNode.removeChild(headerElement);
                    }

                    _Dispose._disposeElement(headerElement);

                    group.header = null;
                    group.left = -1;
                    group.top = -1;

                    if (focusedItemPurged) {
                        this._listView._setFocusOnItem(this._listView._selection._getFocused());
                    }
                },

                _unrealizeItems: function VirtualizeContentsView_unrealizeItems(remove) {
                    var that = this,
                        removedCount = 0;

                    this.items.eachIndex(function (index) {
                        if (index < that.begin || index >= that.end) {
                            that._unrealizeItem(index);
                            return remove && ++removedCount >= remove;
                        }
                    });

                    var groups = this._listView._groups,
                        beginGroup = groups.groupFromItem(this.begin);

                    if (beginGroup !== null) {
                        var endGroup = groups.groupFromItem(this.end - 1);
                        for (var i = 0, len = groups.length() ; i < len; i++) {
                            var group = groups.group(i);
                            if ((i < beginGroup || i > endGroup) && group.header) {
                                this._unrealizeGroup(group);
                            }
                        }
                    }
                },

                _unrealizeExcessiveItems: function VirtualizeContentsView_unrealizeExcessiveItems() {
                    var realized = this.items.count(),
                        needed = this.end - this.begin,
                        approved = needed + this._listView._maxDeferredItemCleanup;

                    this._listView._writeProfilerMark("_unrealizeExcessiveItems realized(" + realized + ") approved(" + approved + "),info");
                    if (realized > approved) {
                        this._unrealizeItems(realized - approved);
                    }
                },

                _lazilyUnrealizeItems: function VirtualizeContentsView_lazilyUnrealizeItems() {
                    this._listView._writeProfilerMark("_lazilyUnrealizeItems,StartTM");
                    var that = this;
                    return waitForSeZo(this._listView).then(function () {

                        function done() {
                            that._listView._writeProfilerMark("_lazilyUnrealizeItems,StopTM");
                        }

                        if (that._listView._isZombie()) {
                            done();
                            return;
                        }

                        var itemsToUnrealize = [];
                        that.items.eachIndex(function (index) {
                            if (index < that.begin || index >= that.end) {
                                itemsToUnrealize.push(index);
                            }
                        });

                        that._listView._writeProfilerMark("_lazilyUnrealizeItems itemsToUnrealize(" + itemsToUnrealize.length + "),info");

                        var groupsToUnrealize = [],
                            groups = that._listView._groups,
                            beginGroup = groups.groupFromItem(that.begin);

                        if (beginGroup !== null) {
                            var endGroup = groups.groupFromItem(that.end - 1);
                            for (var i = 0, len = groups.length() ; i < len; i++) {
                                var group = groups.group(i);
                                if ((i < beginGroup || i > endGroup) && group.header) {
                                    groupsToUnrealize.push(group);
                                }
                            }
                        }

                        if (itemsToUnrealize.length || groupsToUnrealize.length) {
                            var job;

                            var promise = new Promise(function (complete) {

                                function unrealizeWorker(info) {
                                    if (that._listView._isZombie()) { return; }

                                    var firstIndex = -1,
                                        lastIndex = -1,
                                        removeCount = 0,
                                        zooming = shouldWaitForSeZo(that._listView);

                                    while (itemsToUnrealize.length && !zooming && !info.shouldYield) {
                                        var itemIndex = itemsToUnrealize.shift();
                                        that._unrealizeItem(itemIndex);

                                        removeCount++;
                                        if (firstIndex < 0) {
                                            firstIndex = itemIndex;
                                        }
                                        lastIndex = itemIndex;
                                    }
                                    that._listView._writeProfilerMark("unrealizeWorker removeItems:" + removeCount + " (" + firstIndex + "-" + lastIndex + "),info");

                                    while (groupsToUnrealize.length && !zooming && !info.shouldYield) {
                                        that._unrealizeGroup(groupsToUnrealize.shift());
                                    }

                                    if (itemsToUnrealize.length || groupsToUnrealize.length) {
                                        if (zooming) {
                                            info.setPromise(waitForSeZo(that._listView).then(function () {
                                                return unrealizeWorker;
                                            }));
                                        } else {
                                            info.setWork(unrealizeWorker);
                                        }
                                    } else {
                                        complete();
                                    }
                                }

                                job = Scheduler.schedule(unrealizeWorker, Scheduler.Priority.belowNormal, null, "WinJS.UI.ListView._lazilyUnrealizeItems");
                            });

                            return promise.then(done, function (error) {
                                job.cancel();
                                that._listView._writeProfilerMark("_lazilyUnrealizeItems canceled,info");
                                that._listView._writeProfilerMark("_lazilyUnrealizeItems,StopTM");
                                return Promise.wrapError(error);
                            });

                        } else {
                            done();
                            return Promise.wrap();
                        }
                    });
                },

                _getBoundingRectString: function VirtualizeContentsView_getBoundingRectString(itemIndex) {
                    var result;
                    if (itemIndex >= 0 && itemIndex < this.containers.length) {
                        var itemPos = this._listView._layout._getItemPosition(itemIndex);
                        if (itemPos) {
                            result = "[" + itemPos.left + "; " + itemPos.top + "; " + itemPos.width + "; " + itemPos.height + " ]";
                        }
                    }
                    return result || "";
                },

                _clearDeferTimeout: function VirtualizeContentsView_clearDeferTimeout() {
                    if (this.deferTimeout) {
                        this.deferTimeout.cancel();
                        this.deferTimeout = null;
                    }
                    if (this.deferredActionCancelToken !== -1) {
                        this._listView._versionManager.clearCancelOnNotification(this.deferredActionCancelToken);
                        this.deferredActionCancelToken = -1;
                    }
                },

                _setupAria: function VirtualizeContentsView_setupAria(timedOut) {
                    if (this._listView._isZombie()) { return; }
                    var that = this;

                    function done() {
                        that._listView._writeProfilerMark("aria work,StopTM");
                    }

                    function calcLastRealizedIndexInGroup(groupIndex) {
                        var groups = that._listView._groups,
                            nextGroup = groups.group(groupIndex + 1);
                        return (nextGroup ? Math.min(nextGroup.startIndex - 1, that.end - 1) : that.end - 1);
                    }

                    this._listView._createAriaMarkers();
                    return this._listView._itemsCount().then(function (count) {
                        if (count > 0 && that.firstIndexDisplayed !== -1 && that.lastIndexDisplayed !== -1) {
                            that._listView._writeProfilerMark("aria work,StartTM");
                            var startMarker = that._listView._ariaStartMarker,
                                endMarker = that._listView._ariaEndMarker,
                                index = that.begin,
                                item = that.items.itemAt(that.begin),
                                job,
                                // These are only used when the ListView is using groups
                                groups,
                                startGroup,
                                currentGroup,
                                group,
                                lastRealizedIndexInGroup;

                            if (item) {
                                _ElementUtilities._ensureId(item);
                                if (that._listView._groupsEnabled()) {
                                    groups = that._listView._groups;
                                    startGroup = currentGroup = groups.groupFromItem(that.begin);
                                    group = groups.group(currentGroup);
                                    lastRealizedIndexInGroup = calcLastRealizedIndexInGroup(currentGroup);
                                    _ElementUtilities._ensureId(group.header);
                                    _ElementUtilities._setAttribute(group.header, "role", that._listView._headerRole);
                                    _ElementUtilities._setAttribute(group.header, "x-ms-aria-flowfrom", startMarker.id);
                                    setFlow(group.header, item);
                                    _ElementUtilities._setAttribute(group.header, "tabindex", that._listView._tabIndex);
                                } else {
                                    _ElementUtilities._setAttribute(item, "x-ms-aria-flowfrom", startMarker.id);
                                }

                                return new Promise(function (completeJobPromise) {
                                    var skipWait = timedOut;
                                    job = Scheduler.schedule(function ariaWorker(jobInfo) {
                                        if (that._listView._isZombie()) {
                                            done();
                                            return;
                                        }

                                        for (; index < that.end; index++) {
                                            if (!skipWait && shouldWaitForSeZo(that._listView)) {
                                                jobInfo.setPromise(waitForSeZo(that._listView).then(function (timedOut) {
                                                    skipWait = timedOut;
                                                    return ariaWorker;
                                                }));
                                                return;
                                            } else if (jobInfo.shouldYield) {
                                                jobInfo.setWork(ariaWorker);
                                                return;
                                            }

                                            item = that.items.itemAt(index);
                                            var nextItem = that.items.itemAt(index + 1);

                                            if (nextItem) {
                                                _ElementUtilities._ensureId(nextItem);
                                            }

                                            _ElementUtilities._setAttribute(item, "role", that._listView._itemRole);
                                            _ElementUtilities._setAttribute(item, "aria-setsize", count);
                                            _ElementUtilities._setAttribute(item, "aria-posinset", index + 1);
                                            _ElementUtilities._setAttribute(item, "tabindex", that._listView._tabIndex);

                                            if (that._listView._groupsEnabled()) {
                                                if (index === lastRealizedIndexInGroup || !nextItem) {
                                                    var nextGroup = groups.group(currentGroup + 1);

                                                    // If group is the last realized group, then nextGroup won't exist in the DOM.
                                                    // When this is the case, nextItem shouldn't exist.
                                                    if (nextGroup && nextGroup.header && nextItem) {
                                                        _ElementUtilities._setAttribute(nextGroup.header, "tabindex", that._listView._tabIndex);
                                                        _ElementUtilities._setAttribute(nextGroup.header, "role", that._listView._headerRole);
                                                        _ElementUtilities._ensureId(nextGroup.header);
                                                        setFlow(item, nextGroup.header);
                                                        setFlow(nextGroup.header, nextItem);
                                                    } else {
                                                        // We're at the last group so flow to the end marker
                                                        _ElementUtilities._setAttribute(item, "aria-flowto", endMarker.id);
                                                    }

                                                    currentGroup++;
                                                    group = nextGroup;
                                                    lastRealizedIndexInGroup = calcLastRealizedIndexInGroup(currentGroup);
                                                } else {
                                                    // This is not the last item in the group so flow to the next item
                                                    setFlow(item, nextItem);
                                                }
                                            } else if (nextItem) {
                                                // Groups are disabled so as long as we aren't at the last item, flow to the next one
                                                setFlow(item, nextItem);
                                            } else {
                                                // Groups are disabled and we're at the last item, so flow to the end marker
                                                _ElementUtilities._setAttribute(item, "aria-flowto", endMarker.id);
                                            }
                                            if (!nextItem) {
                                                break;
                                            }
                                        }

                                        that._listView._fireAccessibilityAnnotationCompleteEvent(that.begin, index, startGroup, currentGroup - 1);

                                        done();
                                        completeJobPromise();
                                    }, Scheduler.Priority.belowNormal, null, "WinJS.UI.ListView._setupAria");
                                }, function () {
                                    // Cancellation handler for promise returned by setupAria
                                    job.cancel();
                                    done();
                                });
                            } else {
                                // the first item is null
                                done();
                            }
                        } else {
                            // The count is 0
                            return Promise.wrap();
                        }
                    });
                },

                _setupDeferredActions: function VirtualizeContentsView_setupDeferredActions() {
                    this._listView._writeProfilerMark("_setupDeferredActions,StartTM");
                    var that = this;

                    this._clearDeferTimeout();

                    function cleanUp() {
                        if (that._listView._isZombie()) { return; }
                        that.deferTimeout = null;
                        that._listView._versionManager.clearCancelOnNotification(that.deferredActionCancelToken);
                        that.deferredActionCancelToken = -1;
                    }

                    this.deferTimeout = this._lazilyRemoveRedundantItemsBlocks().then(function () {
                            return Promise.timeout(_Constants._DEFERRED_ACTION);
                        }).
                        then(function () {
                            return waitForSeZo(that._listView);
                        }).
                        then(function (timedOut) {
                            return that._setupAria(timedOut);
                        }).
                        then(cleanUp, function (error) {
                            cleanUp();
                            return Promise.wrapError(error);
                        });

                    this.deferredActionCancelToken = this._listView._versionManager.cancelOnNotification(this.deferTimeout);
                    this._listView._writeProfilerMark("_setupDeferredActions,StopTM");
                },

                // Sets aria-flowto on _ariaStartMarker and x-ms-aria-flowfrom on _ariaEndMarker. The former
                // points to either the first visible group header or the first visible item. The latter points
                // to the last visible item.
                _updateAriaMarkers: function VirtualizeContentsView_updateAriaMarkers(listViewIsEmpty, firstIndexDisplayed, lastIndexDisplayed) {
                    var that = this;
                    if (this._listView._isZombie()) {
                        return;
                    }

                    function getFirstVisibleItem() {
                        return that.items.itemAt(firstIndexDisplayed);
                    }

                    // At a certain index, the VDS may return null for all items at that index and
                    // higher. When this is the case, the end marker should point to the last
                    // non-null item in the visible range.
                    function getLastVisibleItem() {
                        for (var i = lastIndexDisplayed; i >= firstIndexDisplayed; i--) {
                            if (that.items.itemAt(i)) {
                                return that.items.itemAt(i);
                            }
                        }
                        return null;
                    }

                    this._listView._createAriaMarkers();
                    var startMarker = this._listView._ariaStartMarker,
                        endMarker = this._listView._ariaEndMarker,
                        firstVisibleItem,
                        lastVisibleItem;

                    if (firstIndexDisplayed !== -1 && lastIndexDisplayed !== -1 && firstIndexDisplayed <= lastIndexDisplayed) {
                        firstVisibleItem = getFirstVisibleItem();
                        lastVisibleItem = getLastVisibleItem();
                    }

                    if (listViewIsEmpty || !firstVisibleItem || !lastVisibleItem) {
                        setFlow(startMarker, endMarker);
                        this._listView._fireAccessibilityAnnotationCompleteEvent(-1, -1);
                    } else {
                        _ElementUtilities._ensureId(firstVisibleItem);
                        _ElementUtilities._ensureId(lastVisibleItem);

                        // Set startMarker's flowto
                        if (this._listView._groupsEnabled()) {
                            var groups = this._listView._groups,
                                firstVisibleGroup = groups.group(groups.groupFromItem(firstIndexDisplayed));

                            if (firstVisibleGroup.header) {
                                _ElementUtilities._ensureId(firstVisibleGroup.header);

                                if (firstIndexDisplayed === firstVisibleGroup.startIndex) {
                                    _ElementUtilities._setAttribute(startMarker, "aria-flowto", firstVisibleGroup.header.id);
                                } else {
                                    _ElementUtilities._setAttribute(startMarker, "aria-flowto", firstVisibleItem.id);
                                }
                            }
                        } else {
                            _ElementUtilities._setAttribute(startMarker, "aria-flowto", firstVisibleItem.id);
                        }

                        // Set endMarker's flowfrom
                        _ElementUtilities._setAttribute(endMarker, "x-ms-aria-flowfrom", lastVisibleItem.id);
                    }
                },

                // Update the ARIA attributes on item that are needed so that Narrator can announce it.
                // item must be in the items container.
                updateAriaForAnnouncement: function VirtualizeContentsView_updateAriaForAnnouncement(item, count) {
                    var index = -1;
                    var type = _UI.ObjectType.item;
                    if (_ElementUtilities.hasClass(item, _Constants._headerClass)) {
                        index = this._listView._groups.index(item);
                        type = _UI.ObjectType.groupHeader;
                        _ElementUtilities._setAttribute(item, "role", this._listView._headerRole);
                        _ElementUtilities._setAttribute(item, "tabindex", this._listView._tabIndex);
                    } else {
                        index = this.items.index(item);
                        _ElementUtilities._setAttribute(item, "aria-setsize", count);
                        _ElementUtilities._setAttribute(item, "aria-posinset", index + 1);
                        _ElementUtilities._setAttribute(item, "role", this._listView._itemRole);
                        _ElementUtilities._setAttribute(item, "tabindex", this._listView._tabIndex);
                    }

                    if (type === _UI.ObjectType.groupHeader) {
                        this._listView._fireAccessibilityAnnotationCompleteEvent(-1, -1, index, index);
                    } else {
                        this._listView._fireAccessibilityAnnotationCompleteEvent(index, index, -1, -1);
                    }
                },

                _reportElementsLevel: function VirtualizeContentsView_reportElementsLevel(direction) {
                    var items = this.items;

                    function elementsCount(first, last) {
                        var count = 0;
                        for (var itemIndex = first; itemIndex <= last; itemIndex++) {
                            var itemData = items.itemDataAt(itemIndex);
                            if (itemData && itemData.container) {
                                count++;
                            }
                        }
                        return count;
                    }

                    var level;
                    if (direction === "right") {
                        level = Math.floor(100 * elementsCount(this.firstIndexDisplayed, this.end - 1) / (this.end - this.firstIndexDisplayed));
                    } else {
                        level = Math.floor(100 * elementsCount(this.begin, this.lastIndexDisplayed) / (this.lastIndexDisplayed - this.begin + 1));
                    }

                    this._listView._writeProfilerMark("elementsLevel level(" + level + "),info");
                },

                _createHeaderContainer: function VirtualizeContentsView_createHeaderContainer(insertAfter) {
                    return this._createSurfaceChild(_Constants._headerContainerClass, insertAfter);
                },

                _createItemsContainer: function VirtualizeContentsView_createItemsContainer(insertAfter) {
                    var itemsContainer = this._createSurfaceChild(_Constants._itemsContainerClass, insertAfter);
                    var padder = _Global.document.createElement("div");
                    padder.className = _Constants._padderClass;
                    itemsContainer.appendChild(padder);
                    return itemsContainer;
                },

                _ensureContainerInDOM: function VirtualizeContentsView_ensureContainerInDOM(index) {
                    var container = this.containers[index];
                    if (container && !this._listView._canvas.contains(container)) {
                        this._forceItemsBlocksInDOM(index, index + 1);
                        return true;
                    }
                    return false;
                },

                _ensureItemsBlocksInDOM: function VirtualizeContentsView_ensureItemsBlocksInDOM(begin, end) {
                    if (this._expandedRange) {
                        var oldBegin = this._expandedRange.first.index,
                            oldEnd = this._expandedRange.last.index + 1;

                        if (begin <= oldBegin && end > oldBegin) {
                            end = Math.max(end, oldEnd);
                        } else if (begin < oldEnd && end >= oldEnd) {
                            begin = Math.min(begin, oldBegin);
                        }
                    }
                    this._forceItemsBlocksInDOM(begin, end);
                },

                _removeRedundantItemsBlocks: function VirtualizeContentsView_removeRedundantItemsBlocks() {
                    if (this.begin !== -1 && this.end !== -1) {
                        this._forceItemsBlocksInDOM(this.begin, this.end);
                    }
                },

                _lazilyRemoveRedundantItemsBlocks: function VirtualizeContentsView_lazilyRemoveRedundantItemsBlocks() {
                    this._listView._writeProfilerMark("_lazilyRemoveRedundantItemsBlocks,StartTM");
                    var that = this;
                    return waitForSeZo(this._listView).then(function () {

                        function done() {
                            that._listView._writeProfilerMark("_lazilyRemoveRedundantItemsBlocks,StopTM");
                        }

                        if (that._listView._isZombie()) {
                            done();
                            return;
                        }

                        if (that._expandedRange && that.begin !== -1 && that.end !== -1 && (that._expandedRange.first.index < that.begin || that._expandedRange.last.index + 1 > that.end)) {
                            var job;

                            var promise = new Promise(function (complete) {

                                function blocksCleanupWorker(info) {
                                    if (that._listView._isZombie()) { return; }

                                    var zooming = shouldWaitForSeZo(that._listView);

                                    while (that._expandedRange.first.index < that.begin && !zooming && !info.shouldYield) {
                                        var begin = Math.min(that.begin, that._expandedRange.first.index + that._blockSize * _VirtualizeContentsView._blocksToRelease);
                                        that._forceItemsBlocksInDOM(begin, that.end);
                                    }

                                    while (that._expandedRange.last.index + 1 > that.end && !zooming && !info.shouldYield) {
                                        var end = Math.max(that.end, that._expandedRange.last.index - that._blockSize * _VirtualizeContentsView._blocksToRelease);
                                        that._forceItemsBlocksInDOM(that.begin, end);
                                    }

                                    if (that._expandedRange.first.index < that.begin || that._expandedRange.last.index + 1 > that.end) {
                                        if (zooming) {
                                            info.setPromise(waitForSeZo(that._listView).then(function () {
                                                return blocksCleanupWorker;
                                            }));
                                        } else {
                                            info.setWork(blocksCleanupWorker);
                                        }
                                    } else {
                                        complete();
                                    }
                                }

                                job = Scheduler.schedule(blocksCleanupWorker, Scheduler.Priority.belowNormal, null, "WinJS.UI.ListView._lazilyRemoveRedundantItemsBlocks");
                            });

                            return promise.then(done, function (error) {
                                job.cancel();
                                that._listView._writeProfilerMark("_lazilyRemoveRedundantItemsBlocks canceled,info");
                                that._listView._writeProfilerMark("_lazilyRemoveRedundantItemsBlocks,StopTM");
                                return Promise.wrapError(error);
                            });

                        } else {
                            done();
                            return Promise.wrap();
                        }
                    });
                },

                _forceItemsBlocksInDOM: function VirtualizeContentsView_forceItemsBlocksInDOM(begin, end) {
                    if (!this._blockSize) {
                        return;
                    }
                    var perfId = "_forceItemsBlocksInDOM begin(" + begin + ") end(" + end + "),";
                    this._listView._writeProfilerMark(perfId + "StartTM");

                    var that = this,
                        added = 0,
                        removed = 0,
                        paddingProperty = "padding" + (this._listView._horizontal() ? "Left" : "Top");

                    function setPadder(itemsContainer, padding) {
                        var padder = itemsContainer.element.firstElementChild;
                        padder.style[paddingProperty] = padding;
                    }

                    function forEachBlock(callback) {
                        for (var g = 0; g < that.tree.length; g++) {
                            var itemsContainer = that.tree[g].itemsContainer;
                            for (var b = 0, len = itemsContainer.itemsBlocks.length; b < len; b++) {
                                if (callback(itemsContainer, itemsContainer.itemsBlocks[b])) {
                                    return;
                                }
                            }
                        }
                    }

                    function measureItemsBlock(itemsBlock) {
                        that._listView._writeProfilerMark("_itemsBlockExtent,StartTM");
                        that._listView._itemsBlockExtent = _ElementUtilities[that._listView._horizontal() ? "getTotalWidth" : "getTotalHeight"](itemsBlock.element);
                        that._listView._writeProfilerMark("_itemsBlockExtent(" + that._listView._itemsBlockExtent + "),info");
                        that._listView._writeProfilerMark("_itemsBlockExtent,StopTM");
                    }

                    function getItemsBlockExtent() {
                        if (that._listView._itemsBlockExtent === -1) {
                            // first try blocks already added to the DOM
                            forEachBlock(function (itemsContainer, itemsBlock) {
                                if (itemsBlock.items.length === that._blockSize && itemsBlock.element.parentNode === itemsContainer.element) {
                                    measureItemsBlock(itemsBlock);
                                    return true;
                                }
                                return false;
                            });
                        }

                        if (that._listView._itemsBlockExtent === -1) {
                            forEachBlock(function (itemsContainer, itemsBlock) {
                                if (itemsBlock.items.length === that._blockSize) {
                                    itemsContainer.element.appendChild(itemsBlock.element);
                                    measureItemsBlock(itemsBlock);
                                    itemsContainer.element.removeChild(itemsBlock.element);
                                    return true;
                                }
                                return false;
                            });
                        }
                        return that._listView._itemsBlockExtent;
                    }

                    function removeBlocks(itemsContainer, begin, end) {

                        function remove(blockIndex) {
                            var block = itemsContainer.itemsBlocks[blockIndex];
                            if (block && block.element.parentNode === itemsContainer.element) {
                                itemsContainer.element.removeChild(block.element);
                                removed++;
                            }
                        }

                        if (Array.isArray(begin)) {
                            begin.forEach(remove);
                        } else {
                            for (var i = begin; i < end; i++) {
                                remove(i);
                            }
                        }
                    }

                    function addBlocks(itemsContainer, begin, end) {
                        var padder = itemsContainer.element.firstElementChild,
                            previous = padder;

                        for (var i = begin; i < end; i++) {
                            var block = itemsContainer.itemsBlocks[i];
                            if (block) {
                                if (block.element.parentNode !== itemsContainer.element) {
                                    itemsContainer.element.insertBefore(block.element, previous.nextElementSibling);
                                    added++;
                                }
                                previous = block.element;
                            }
                        }
                    }

                    function collapseGroup(groupIndex) {
                        if (groupIndex < that.tree.length) {
                            that._listView._writeProfilerMark("collapseGroup(" + groupIndex + "),info");
                            var itemsContainer = that.tree[groupIndex].itemsContainer;
                            removeBlocks(itemsContainer, 0, itemsContainer.itemsBlocks.length);
                            setPadder(itemsContainer, "");
                        }
                    }

                    function expandGroup(groupIndex) {
                        if (groupIndex < that.tree.length) {
                            that._listView._writeProfilerMark("expandGroup(" + groupIndex + "),info");
                            var itemsContainer = that.tree[groupIndex].itemsContainer;
                            addBlocks(itemsContainer, 0, itemsContainer.itemsBlocks.length);
                            setPadder(itemsContainer, "");
                        }
                    }

                    function removedFromRange(oldRange, newRange) {
                        function expand(first, last) {
                            var array = [];
                            for (var i = first; i <= last; i++) {
                                array.push(i);
                            }
                            return array;
                        }

                        var newL = newRange[0];
                        var newR = newRange[1];
                        var oldL = oldRange[0];
                        var oldR = oldRange[1];

                        if (newR < oldL || newL > oldR) {
                            return expand(oldL, oldR);
                        } else if (newL > oldL && newR < oldR) {
                            return expand(oldL, newL - 1).concat(expand(newR + 1, oldR));
                        } else if (oldL < newL) {
                            return expand(oldL, newL - 1);
                        } else if (oldR > newR) {
                            return expand(newR + 1, oldR);
                        } else {
                            return null;
                        }
                    }

                    var firstGroupIndex = this._listView._groups.groupFromItem(begin),
                        lastGroupIndex = this._listView._groups.groupFromItem(end - 1);

                    var firstGroup = this._listView._groups.group(firstGroupIndex),
                        firstItemsContainer = that.tree[firstGroupIndex].itemsContainer;

                    var firstBlock = Math.floor((begin - firstGroup.startIndex) / this._blockSize);

                    var lastGroup = this._listView._groups.group(lastGroupIndex),
                        lastItemsContainer = that.tree[lastGroupIndex].itemsContainer;

                    var lastBlock = Math.floor((end - 1 - lastGroup.startIndex) / this._blockSize);

                    // if size of structure block is needed try to obtain it before modifying the tree to avoid a layout pass
                    if (firstBlock && that._listView._itemsBlockExtent === -1) {
                        forEachBlock(function (itemsContainer, itemsBlock) {
                            if (itemsBlock.items.length === that._blockSize && itemsBlock.element.parentNode === itemsContainer.element) {
                                measureItemsBlock(itemsBlock);
                                return true;
                            }
                            return false;
                        });
                    }

                    var groupsToCollapse = this._expandedRange ? removedFromRange([this._expandedRange.first.groupIndex, this._expandedRange.last.groupIndex], [firstGroupIndex, lastGroupIndex]) : null;
                    if (groupsToCollapse) {
                        groupsToCollapse.forEach(collapseGroup);
                    }

                    if (this._expandedRange && this._expandedRange.first.groupKey === firstGroup.key) {
                        var blocksToRemove = removedFromRange([this._expandedRange.first.block, Number.MAX_VALUE], [firstBlock, Number.MAX_VALUE]);
                        if (blocksToRemove) {
                            removeBlocks(firstItemsContainer, blocksToRemove);
                        }
                    } else if (this._expandedRange && firstGroupIndex >= this._expandedRange.first.groupIndex && firstGroupIndex <= this._expandedRange.last.groupIndex) {
                        removeBlocks(firstItemsContainer, 0, firstBlock);
                    }

                    if (firstGroupIndex !== lastGroupIndex) {
                        addBlocks(firstItemsContainer, firstBlock, firstItemsContainer.itemsBlocks.length);
                        addBlocks(lastItemsContainer, 0, lastBlock + 1);
                    } else {
                        addBlocks(firstItemsContainer, firstBlock, lastBlock + 1);
                    }

                    if (this._expandedRange && this._expandedRange.last.groupKey === lastGroup.key) {
                        var blocksToRemove = removedFromRange([0, this._expandedRange.last.block], [0, lastBlock]);
                        if (blocksToRemove) {
                            removeBlocks(lastItemsContainer, blocksToRemove);
                        }
                    } else if (this._expandedRange && lastGroupIndex >= this._expandedRange.first.groupIndex && lastGroupIndex <= this._expandedRange.last.groupIndex) {
                        removeBlocks(lastItemsContainer, lastBlock + 1, lastItemsContainer.itemsBlocks.length);
                    }

                    setPadder(firstItemsContainer, firstBlock ? firstBlock * getItemsBlockExtent() + "px" : "");

                    if (firstGroupIndex !== lastGroupIndex) {
                        setPadder(lastItemsContainer, "");
                    }

                    // groups between first and last
                    for (var i = firstGroupIndex + 1; i < lastGroupIndex; i++) {
                        expandGroup(i);
                    }

                    this._expandedRange = {
                        first: {
                            index: begin,
                            groupIndex: firstGroupIndex,
                            groupKey: firstGroup.key,
                            block: firstBlock
                        },
                        last: {
                            index: end - 1,
                            groupIndex: lastGroupIndex,
                            groupKey: lastGroup.key,
                            block: lastBlock
                        },
                    };
                    this._listView._writeProfilerMark("_forceItemsBlocksInDOM groups(" + firstGroupIndex + "-" + lastGroupIndex + ") blocks(" + firstBlock + "-" + lastBlock + ") added(" + added + ") removed(" + removed + "),info");
                    this._listView._writeProfilerMark(perfId + "StopTM");
                },

                _realizePageImpl: function VirtualizeContentsView_realizePageImpl() {
                    var that = this;

                    var perfId = "realizePage(scrollPosition:" + this._scrollbarPos + " forceLayout:" + this._forceRelayout + ")";
                    this._listView._writeProfilerMark(perfId + ",StartTM");

                    // It's safe to skip realizePage, so we just queue up the last request to run when the version manager
                    // get unlocked.
                    //
                    if (this._listView._versionManager.locked) {
                        this._listView._versionManager.unlocked.done(function () {
                            if (!that._listView._isZombie()) {
                                that._listView._batchViewUpdates(_Constants._ViewChange.realize, _Constants._ScrollToPriority.low, that._listView.scrollPosition);
                            }
                        });
                        this._listView._writeProfilerMark(perfId + ",StopTM");
                        return Promise.cancel;
                    }

                    return new Promise(function (c) {
                        var renderingCompleteSignal = new _Signal();

                        function complete() {
                            c();
                            renderingCompleteSignal.complete();
                        }

                        function viewPortPageRealized() {
                            that._listView._hideProgressBar();
                            that._state.setLoadingState(that._listView._LoadingState.viewPortLoaded);
                            if (that._executeAnimations) {
                                that._setState(RealizingAnimatingState, renderingCompleteSignal.promise);
                            }
                        }

                        function pageRealized(count) {
                            that._updateAriaMarkers(count === 0, that.firstIndexDisplayed, that.lastIndexDisplayed);
                            that._state.setLoadingState && that._state.setLoadingState(that._listView._LoadingState.itemsLoaded);
                        }

                        function finish(count) {
                            that._listView._clearInsertedItems();
                            that._listView._groups.removeElements();
                            viewPortPageRealized();
                            pageRealized(count);
                            complete();
                        }

                        that._state.setLoadingState(that._listView._LoadingState.itemsLoading);
                        if (that._firstLayoutPass) {
                            that._listView._showProgressBar(that._listView._element, "50%", "50%");
                        }

                        var count = that.containers.length;

                        if (count) {
                            // While the zoom animation is played we want to minimize the # of pages
                            // being fetched to improve TtFF for SeZo scenarios
                            var pagesToPrefetch = _VirtualizeContentsView._pagesToPrefetch;
                            var customPagesToPrefetchMax = _VirtualizeContentsView._customPagesToPrefetchMax;
                            var customPagesToPrefetchMin = _VirtualizeContentsView._customPagesToPrefetchMin;
                            if (that._listView._zooming) {
                                pagesToPrefetch = 0;
                                customPagesToPrefetchMax = 0;
                                customPagesToPrefetchMin = 0;
                            }

                            var viewportLength = that._listView._getViewportLength();
                            var pagesBefore, pagesAfter;

                            if (_BaseUtils._isiOS && !_VirtualizeContentsView._disableCustomPagesPrefetch) {
                                pagesBefore = (that._direction === "left" ? customPagesToPrefetchMax : customPagesToPrefetchMin);

                                // Optimize the beginning of the list such that if you scroll, then change direction and start going back towards the beginning of the list,
                                // we maintain a remainder of pages that can be added to pagesAfter. This ensures that at beginning of the list, which is the common case,
                                // we always have customPagesToPrefetchMax ahead, even when the scrolling direction is constantly changing.
                                var pagesShortBehind = Math.max(0, (pagesBefore - (that._scrollbarPos / viewportLength)));
                                pagesAfter = Math.min(customPagesToPrefetchMax, pagesShortBehind + (that._direction === "right" ? customPagesToPrefetchMax : customPagesToPrefetchMin));
                            } else {
                                pagesBefore = pagesToPrefetch;
                                pagesAfter = pagesToPrefetch;
                            }

                            var beginningOffset = Math.max(0, that._scrollbarPos - pagesBefore * viewportLength),
                                  endingOffset = that._scrollbarPos + (1 + pagesAfter) * viewportLength;

                            var range = that._listView._layout.itemsFromRange(beginningOffset, endingOffset - 1);
                            if ((range.firstIndex < 0 || range.firstIndex >= count) && (range.lastIndex < 0 || range.lastIndex >= count)) {
                                that.begin = -1;
                                that.end = -1;
                                that.firstIndexDisplayed = -1;
                                that.lastIndexDisplayed = -1;
                                finish(count);
                            } else {
                                var begin = _ElementUtilities._clamp(range.firstIndex, 0, count - 1),
                                    end = _ElementUtilities._clamp(range.lastIndex + 1, 0, count);

                                var inView = that._listView._layout.itemsFromRange(that._scrollbarPos, that._scrollbarPos + viewportLength - 1),
                                    firstInView = _ElementUtilities._clamp(inView.firstIndex, 0, count - 1),
                                    lastInView = _ElementUtilities._clamp(inView.lastIndex, 0, count - 1);

                                if (that._realizationLevel === _VirtualizeContentsView._realizationLevel.skip && !that.lastRealizePass && firstInView === that.firstIndexDisplayed && lastInView === that.lastIndexDisplayed) {
                                    that.begin = begin;
                                    that.end = begin + Object.keys(that.items._itemData).length;
                                    that._updateHeaders(that._listView._canvas, that.begin, that.end).done(function () {
                                        that.lastRealizePass = null;
                                        finish(count);
                                    });
                                } else if ((that._forceRelayout || begin !== that.begin || end !== that.end || firstInView !== that.firstIndexDisplayed || lastInView !== that.lastIndexDisplayed) && (begin < end) && (beginningOffset < endingOffset)) {
                                    that._listView._writeProfilerMark("realizePage currentInView(" + firstInView + "-" + lastInView + ") previousInView(" + that.firstIndexDisplayed + "-" + that.lastIndexDisplayed + ") change(" + (firstInView - that.firstIndexDisplayed) + "),info");
                                    that._cancelRealize();
                                    // cancelRealize changes the realizePass and resets begin/end
                                    var currentPass = that._realizePass;
                                    that.begin = begin;
                                    that.end = end;
                                    that.firstIndexDisplayed = firstInView;
                                    that.lastIndexDisplayed = lastInView;
                                    that.deletesWithoutRealize = 0;

                                    that._ensureItemsBlocksInDOM(that.begin, that.end);

                                    var realizeWork = that._realizeItems(
                                        that._listView._itemCanvas,
                                        that.begin,
                                        that.end,
                                        count,
                                        currentPass,
                                        that._scrollbarPos,
                                        that._direction,
                                        firstInView,
                                        lastInView,
                                        that._forceRelayout);

                                    that._forceRelayout = false;

                                    var realizePassWork = realizeWork.viewportItemsRealized.then(function () {
                                        viewPortPageRealized();
                                        return realizeWork.allItemsRealized;
                                    }).then(function () {
                                        if (that._realizePass === currentPass) {
                                            return that._updateHeaders(that._listView._canvas, that.begin, that.end).then(function () {
                                                pageRealized(count);
                                            });
                                        }
                                    }).then(function () {
                                        return realizeWork.loadingCompleted;
                                    }).then(
                                        function () {
                                            that._unrealizeExcessiveItems();
                                            that.lastRealizePass = null;
                                            complete();
                                        },
                                        function (e) {
                                            if (that._realizePass === currentPass) {
                                                that.lastRealizePass = null;
                                                that.begin = -1;
                                                that.end = -1;
                                            }
                                            return Promise.wrapError(e);
                                        }
                                    );

                                    that.lastRealizePass = Promise.join([realizeWork.viewportItemsRealized, realizeWork.allItemsRealized, realizeWork.loadingCompleted, realizePassWork]);

                                    that._unrealizeExcessiveItems();

                                } else if (!that.lastRealizePass) {
                                    // We are currently in the "itemsLoading" state and need to get back to "complete". The
                                    // previous realize pass has been completed so proceed to the other states.
                                    finish(count);
                                } else {
                                    that.lastRealizePass.then(complete);
                                }
                            }
                        } else {
                            that.begin = -1;
                            that.end = -1;
                            that.firstIndexDisplayed = -1;
                            that.lastIndexDisplayed = -1;

                            finish(count);
                        }

                        that._reportElementsLevel(that._direction);

                        that._listView._writeProfilerMark(perfId + ",StopTM");
                    });
                },

                realizePage: function VirtualizeContentsView_realizePage(scrollToFunctor, forceRelayout, scrollEndPromise, StateType) {
                    this._scrollToFunctor = makeFunctor(scrollToFunctor);
                    this._forceRelayout = this._forceRelayout || forceRelayout;
                    this._scrollEndPromise = scrollEndPromise;

                    this._listView._writeProfilerMark(this._state.name + "_realizePage,info");
                    this._state.realizePage(StateType || RealizingState);
                },

                onScroll: function VirtualizeContentsView_onScroll(scrollToFunctor, scrollEndPromise) {
                    this.realizePage(scrollToFunctor, false, scrollEndPromise, ScrollingState);
                },

                reload: function VirtualizeContentsView_reload(scrollToFunctor, highPriority) {
                    if (this._listView._isZombie()) { return; }

                    this._scrollToFunctor = makeFunctor(scrollToFunctor);
                    this._forceRelayout = true;
                    this._highPriorityRealize = !!highPriority;

                    this.stopWork(true);

                    this._listView._writeProfilerMark(this._state.name + "_rebuildTree,info");
                    this._state.rebuildTree();
                },

                refresh: function VirtualizeContentsView_refresh(scrollToFunctor) {
                    if (this._listView._isZombie()) { return; }

                    this._scrollToFunctor = makeFunctor(scrollToFunctor);
                    this._forceRelayout = true;
                    this._highPriorityRealize = true;

                    this.stopWork();

                    this._listView._writeProfilerMark(this._state.name + "_relayout,info");
                    this._state.relayout();
                },

                waitForValidScrollPosition: function VirtualizeContentsView_waitForValidScrollPosition(newPosition) {
                    var that = this;
                    var currentMaxScroll = this._listView._viewport[this._listView._scrollLength] - this._listView._getViewportLength();
                    if (newPosition > currentMaxScroll) {
                        return that._listView._itemsCount().then(function (count) {
                            // Wait until we have laid out enough containers to be able to set the scroll position to newPosition
                            if (that.containers.length < count) {
                                return Promise._cancelBlocker(that._creatingContainersWork && that._creatingContainersWork.promise).then(function () {
                                    return that._getLayoutCompleted();
                                }).then(function () {
                                    return newPosition;
                                });
                            } else {
                                return newPosition;
                            }
                        });
                    } else {
                        return Promise.wrap(newPosition);
                    }
                },

                waitForEntityPosition: function VirtualizeContentsView_waitForEntityPosition(entity) {
                    var that = this;
                    this._listView._writeProfilerMark(this._state.name + "_waitForEntityPosition" + "(" + entity.type + ": " + entity.index + ")" + ",info");
                    return Promise._cancelBlocker(this._state.waitForEntityPosition(entity).then(function () {
                        if ((entity.type !== _UI.ObjectType.groupHeader && entity.index >= that.containers.length) ||
                            (entity.type === _UI.ObjectType.groupHeader && that._listView._groups.group(entity.index).startIndex >= that.containers.length)) {
                            return that._creatingContainersWork && that._creatingContainersWork.promise;
                        }
                    }).then(function () {
                        return that._getLayoutCompleted();
                    }));
                },

                stopWork: function VirtualizeContentsView_stopWork(stopTreeCreation) {
                    this._listView._writeProfilerMark(this._state.name + "_stop,info");
                    this._state.stop(stopTreeCreation);

                    if (this._layoutWork) {
                        this._layoutWork.cancel();
                    }

                    if (stopTreeCreation && this._creatingContainersWork) {
                        this._creatingContainersWork.cancel();
                    }

                    if (stopTreeCreation) {
                        this._state = new CreatedState(this);
                    }
                },

                _cancelRealize: function VirtualizeContentsView_cancelRealize() {
                    this._listView._writeProfilerMark("_cancelRealize,StartTM");

                    if (this.lastRealizePass || this.deferTimeout) {
                        this._forceRelayout = true;
                    }

                    this._clearDeferTimeout();
                    this._realizePass++;

                    if (this._headerRenderPromises) {
                        this._headerRenderPromises.cancel();
                        this._headerRenderPromises = null;
                    }

                    var last = this.lastRealizePass;
                    if (last) {
                        this.lastRealizePass = null;
                        this.begin = -1;
                        this.end = -1;
                        last.cancel();
                    }
                    this._listView._writeProfilerMark("_cancelRealize,StopTM");
                },

                resetItems: function VirtualizeContentsView_resetItems(unparent) {
                    if (!this._listView._isZombie()) {
                        this.firstIndexDisplayed = -1;
                        this.lastIndexDisplayed = -1;
                        this._runningAnimations = null;
                        this._executeAnimations = false;

                        var listView = this._listView;
                        this._firstLayoutPass = true;
                        listView._unsetFocusOnItem();
                        if (listView._currentMode().onDataChanged) {
                            listView._currentMode().onDataChanged();
                        }

                        this.items.each(function (index, item) {
                            if (unparent && item.parentNode && item.parentNode.parentNode) {
                                item.parentNode.parentNode.removeChild(item.parentNode);
                            }
                            listView._itemsManager.releaseItem(item);
                            _Dispose._disposeElement(item);
                        });

                        this.items.removeItems();
                        this._deferredReparenting = [];

                        if (unparent) {
                            listView._groups.removeElements();
                        }

                        listView._clearInsertedItems();
                    }
                },

                reset: function VirtualizeContentsView_reset() {
                    this.stopWork(true);
                    this._state = new CreatedState(this);

                    this.resetItems();

                    // when in the zombie state, we let disposal cleanup the ScrollView state
                    //
                    if (!this._listView._isZombie()) {
                        var listView = this._listView;
                        listView._groups.resetGroups();
                        listView._resetCanvas();

                        this.tree = null;
                        this.keyToGroupIndex = null;
                        this.containers = null;
                        this._expandedRange = null;
                    }
                },

                cleanUp: function VirtualizeContentsView_cleanUp() {
                    this.stopWork(true);

                    this._runningAnimations && this._runningAnimations.cancel();
                    var itemsManager = this._listView._itemsManager;
                    this.items.each(function (index, item) {
                        itemsManager.releaseItem(item);
                        _Dispose._disposeElement(item);
                    });
                    this._listView._unsetFocusOnItem();
                    this.items.removeItems();
                    this._deferredReparenting = [];
                    this._listView._groups.resetGroups();
                    this._listView._resetCanvas();

                    this.tree = null;
                    this.keyToGroupIndex = null;
                    this.containers = null;
                    this._expandedRange = null;

                    this.destroyed = true;
                },

                getContainer: function VirtualizeContentsView_getContainer(itemIndex) {
                    return this.containers[itemIndex];
                },

                _getHeaderContainer: function VirtualizeContentsView_getHeaderContainer(groupIndex) {
                    return this.tree[groupIndex].header;
                },

                _getGroups: function VirtualizeContentsView_getGroups(count) {
                    if (this._listView._groupDataSource) {
                        var groupsContainer = this._listView._groups.groups,
                            groups = [];
                        if (count) {
                            for (var i = 0, len = groupsContainer.length; i < len; i++) {
                                var group = groupsContainer[i],
                                    nextStartIndex = i + 1 < len ? groupsContainer[i + 1].startIndex : count;
                                groups.push({
                                    key: group.key,
                                    size: nextStartIndex - group.startIndex
                                });
                            }
                        }
                        return groups;
                    } else {
                        return [{ key: "-1", size: count }];
                    }
                },

                _createChunk: function VirtualizeContentsView_createChunk(groups, count, chunkSize) {
                    var that = this;

                    this._listView._writeProfilerMark("createChunk,StartTM");

                    function addToGroup(itemsContainer, groupSize) {
                        var children = itemsContainer.element.children,
                            oldSize = children.length,
                            toAdd = Math.min(groupSize - itemsContainer.items.length, chunkSize);

                        _SafeHtml.insertAdjacentHTMLUnsafe(itemsContainer.element, "beforeend", _Helpers._repeat("<div class='win-container win-backdrop'></div>", toAdd));

                        for (var i = 0; i < toAdd; i++) {
                            var container = children[oldSize + i];
                            itemsContainer.items.push(container);
                            that.containers.push(container);
                        }
                    }

                    function newGroup(group) {
                        var node = {
                            header: that._listView._groupDataSource ? that._createHeaderContainer() : null,
                            itemsContainer: {
                                element: that._createItemsContainer(),
                                items: []
                            }
                        };


                        that.tree.push(node);
                        that.keyToGroupIndex[group.key] = that.tree.length - 1;
                        addToGroup(node.itemsContainer, group.size);
                    }

                    if (this.tree.length && this.tree.length <= groups.length) {
                        var last = this.tree[this.tree.length - 1],
                            finalSize = groups[this.tree.length - 1].size;

                        // check if the last group in the tree already has all items. If not add items to this group
                        if (last.itemsContainer.items.length < finalSize) {
                            addToGroup(last.itemsContainer, finalSize);
                            this._listView._writeProfilerMark("createChunk,StopTM");
                            return;
                        }
                    }

                    if (this.tree.length < groups.length) {
                        newGroup(groups[this.tree.length]);
                    }

                    this._listView._writeProfilerMark("createChunk,StopTM");
                },

                _createChunkWithBlocks: function VirtualizeContentsView_createChunkWithBlocks(groups, count, blockSize, chunkSize) {
                    var that = this;
                    this._listView._writeProfilerMark("createChunk,StartTM");

                    function addToGroup(itemsContainer, toAdd) {
                        var lastExistingBlock = itemsContainer.itemsBlocks.length ? itemsContainer.itemsBlocks[itemsContainer.itemsBlocks.length - 1] : null;

                        if (lastExistingBlock && lastExistingBlock.items.length < blockSize) {
                            var fix = Math.min(toAdd, blockSize - lastExistingBlock.items.length);
                            _SafeHtml.insertAdjacentHTMLUnsafe(lastExistingBlock.element, "beforeend", _Helpers._repeat("<div class='win-container win-backdrop'></div>", fix));

                            var oldSize = lastExistingBlock.items.length;
                            children = lastExistingBlock.element.children;

                            for (var j = 0; j < fix; j++) {
                                var child = children[oldSize + j];
                                lastExistingBlock.items.push(child);
                                that.containers.push(child);
                            }

                            toAdd -= fix;
                        }

                        if (toAdd > chunkSize) {
                            toAdd = Math.min(toAdd, Math.max(1, Math.floor(chunkSize / blockSize)) * blockSize);
                        }

                        var blocks = Math.floor(toAdd / blockSize),
                            lastBlockSize = toAdd % blockSize;

                        var blockMarkup = "<div class='win-itemsblock'>" + _Helpers._repeat("<div class='win-container win-backdrop'></div>", blockSize) + "</div>",
                            markup = _Helpers._repeat(blockMarkup, blocks);

                        if (lastBlockSize) {
                            markup += "<div class='win-itemsblock'>" + _Helpers._repeat("<div class='win-container win-backdrop'></div>", lastBlockSize) + "</div>";
                            blocks++;
                        }

                        var blocksTemp = _Global.document.createElement("div");
                        _SafeHtml.setInnerHTMLUnsafe(blocksTemp, markup);
                        var children = blocksTemp.children;

                        for (var i = 0; i < blocks; i++) {
                            var block = children[i],
                                blockNode = {
                                    element: block,
                                    items: _Helpers._nodeListToArray(block.children)
                                };
                            itemsContainer.itemsBlocks.push(blockNode);
                            for (var n = 0; n < blockNode.items.length; n++) {
                                that.containers.push(blockNode.items[n]);
                            }
                        }
                    }

                    function newGroup(group) {
                        var node = {
                            header: that._listView._groupDataSource ? that._createHeaderContainer() : null,
                            itemsContainer: {
                                element: that._createItemsContainer(),
                                itemsBlocks: []
                            }
                        };

                        that.tree.push(node);
                        that.keyToGroupIndex[group.key] = that.tree.length - 1;

                        addToGroup(node.itemsContainer, group.size);
                    }

                    if (this.tree.length && this.tree.length <= groups.length) {
                        var lastContainer = this.tree[this.tree.length - 1].itemsContainer,
                            finalSize = groups[this.tree.length - 1].size,
                            currentSize = 0;

                        if (lastContainer.itemsBlocks.length) {
                            currentSize = (lastContainer.itemsBlocks.length - 1) * blockSize + lastContainer.itemsBlocks[lastContainer.itemsBlocks.length - 1].items.length;
                        }

                        if (currentSize < finalSize) {
                            addToGroup(lastContainer, finalSize - currentSize);
                            this._listView._writeProfilerMark("createChunk,StopTM");
                            return;
                        }
                    }

                    if (this.tree.length < groups.length) {
                        newGroup(groups[this.tree.length]);
                    }

                    this._listView._writeProfilerMark("createChunk,StopTM");
                },

                _generateCreateContainersWorker: function VirtualizeContentsView_generateCreateContainersWorker() {
                    var that = this,
                        counter = 0,
                        skipWait = false;

                    return function work(info) {
                        if (!that._listView._versionManager.locked) {
                            that._listView._itemsCount().then(function (count) {
                                var zooming = !skipWait && shouldWaitForSeZo(that._listView);

                                if (!zooming) {
                                    if (that._listView._isZombie()) { return; }

                                    skipWait = false;

                                    var end = _BaseUtils._now() + _VirtualizeContentsView._createContainersJobTimeslice,
                                        groups = that._getGroups(count),
                                        startLength = that.containers.length,
                                        realizedToEnd = that.end === that.containers.length,
                                        chunkSize = _VirtualizeContentsView._chunkSize;

                                    do {
                                        that._blockSize ? that._createChunkWithBlocks(groups, count, that._blockSize, chunkSize) : that._createChunk(groups, count, chunkSize);
                                        counter++;
                                    } while (that.containers.length < count && _BaseUtils._now() < end);

                                    that._listView._writeProfilerMark("createContainers yields containers(" + that.containers.length + "),info");

                                    that._listView._affectedRange.add({ start: startLength, end: that.containers.length }, count);

                                    if (realizedToEnd) {
                                        that.stopWork();
                                        that._listView._writeProfilerMark(that._state.name + "_relayout,info");
                                        that._state.relayout();
                                    } else {
                                        that._listView._writeProfilerMark(that._state.name + "_layoutNewContainers,info");
                                        that._state.layoutNewContainers();
                                    }

                                    if (that.containers.length < count) {
                                        info.setWork(work);
                                    } else {
                                        that._listView._writeProfilerMark("createContainers completed steps(" + counter + "),info");
                                        that._creatingContainersWork.complete();
                                    }
                                } else {
                                    // Waiting on zooming
                                    info.setPromise(waitForSeZo(that._listView).then(function (timedOut) {
                                        skipWait = timedOut;
                                        return work;
                                    }));
                                }
                            });
                        } else {
                            // Version manager locked
                            info.setPromise(that._listView._versionManager.unlocked.then(function () {
                                return work;
                            }));
                        }
                    };
                },

                _scheduleLazyTreeCreation: function VirtualizeContentsView_scheduleLazyTreeCreation() {
                    return Scheduler.schedule(this._generateCreateContainersWorker(), Scheduler.Priority.idle, this, "WinJS.UI.ListView.LazyTreeCreation");
                },

                _createContainers: function VirtualizeContentsView_createContainers() {
                    this.tree = null;
                    this.keyToGroupIndex = null;
                    this.containers = null;
                    this._expandedRange = null;

                    var that = this,
                        count;

                    return this._listView._itemsCount().then(function (c) {
                        if (c === 0) {
                            that._listView._hideProgressBar();
                        }
                        count = c;
                        that._listView._writeProfilerMark("createContainers(" + count + "),StartTM");
                        if (that._listView._groupDataSource) {
                            return that._listView._groups.initialize();
                        }
                    }).then(function () {
                        that._listView._writeProfilerMark("numberOfItemsPerItemsBlock,StartTM");
                        return (count && that._listView._groups.length() ? that._listView._layout.numberOfItemsPerItemsBlock : null);
                    }).then(function (blockSize) {
                        that._listView._writeProfilerMark("numberOfItemsPerItemsBlock(" + blockSize + "),info");
                        that._listView._writeProfilerMark("numberOfItemsPerItemsBlock,StopTM");

                        that._listView._resetCanvas();

                        that.tree = [];
                        that.keyToGroupIndex = {};
                        that.containers = [];
                        that._blockSize = blockSize;

                        var groups = that._getGroups(count);

                        var end = _BaseUtils._now() + _VirtualizeContentsView._maxTimePerCreateContainers,
                            chunkSize = Math.min(_VirtualizeContentsView._startupChunkSize, _VirtualizeContentsView._chunkSize);
                        var stop;
                        do {
                            stop = blockSize ? that._createChunkWithBlocks(groups, count, blockSize, chunkSize) : that._createChunk(groups, count, chunkSize);
                        } while (_BaseUtils._now() < end && that.containers.length < count && !stop);

                        that._listView._writeProfilerMark("createContainers created(" + that.containers.length + "),info");

                        that._listView._affectedRange.add({ start: 0, end: that.containers.length }, count);

                        if (that.containers.length < count) {
                            var jobNode = that._scheduleLazyTreeCreation();

                            that._creatingContainersWork.promise.done(null, function () {
                                jobNode.cancel();
                            });
                        } else {
                            that._listView._writeProfilerMark("createContainers completed synchronously,info");
                            that._creatingContainersWork.complete();
                        }

                        that._listView._writeProfilerMark("createContainers(" + count + "),StopTM");
                    });
                },

                _updateItemsBlocks: function VirtualizeContentsView_updateItemsBlocks(blockSize) {
                    var that = this;
                    var usingStructuralNodes = !!blockSize;

                    function createNewBlock() {
                        var element = _Global.document.createElement("div");
                        element.className = _Constants._itemsBlockClass;
                        return element;
                    }

                    function updateGroup(itemsContainer, startIndex) {
                        var blockElements = [],
                            itemsCount = 0,
                            blocks = itemsContainer.itemsBlocks,
                            b;

                        function rebuildItemsContainer() {
                            itemsContainer.itemsBlocks = null;
                            itemsContainer.items = [];
                            for (var i = 0; i < itemsCount; i++) {
                                var container = that.containers[startIndex + i];
                                itemsContainer.element.appendChild(container);
                                itemsContainer.items.push(container);
                            }
                        }

                        function rebuildItemsContainerWithBlocks() {
                            itemsContainer.itemsBlocks = [{
                                element: blockElements.length ? blockElements.shift() : createNewBlock(),
                                items: []
                            }];
                            var currentBlock = itemsContainer.itemsBlocks[0];
                            for (var i = 0; i < itemsCount; i++) {
                                if (currentBlock.items.length === blockSize) {
                                    var nextBlock = blockElements.length ? blockElements.shift() : createNewBlock();
                                    itemsContainer.itemsBlocks.push({
                                        element: nextBlock,
                                        items: []
                                    });
                                    currentBlock = itemsContainer.itemsBlocks[itemsContainer.itemsBlocks.length - 1];
                                }

                                var container = that.containers[startIndex + i];
                                currentBlock.element.appendChild(container);
                                currentBlock.items.push(container);
                            }
                            itemsContainer.items = null;
                        }

                        if (blocks) {
                            for (b = 0; b < blocks.length; b++) {
                                itemsCount += blocks[b].items.length;
                                blockElements.push(blocks[b].element);
                            }
                        } else {
                            itemsCount = itemsContainer.items.length;
                        }

                        if (usingStructuralNodes) {
                            rebuildItemsContainerWithBlocks();
                        } else {
                            rebuildItemsContainer();
                        }

                        for (b = 0; b < blockElements.length; b++) {
                            var block = blockElements[b];
                            if (block.parentNode === itemsContainer.element) {
                                itemsContainer.element.removeChild(block);
                            }
                        }

                        return itemsCount;
                    }

                    for (var g = 0, startIndex = 0; g < this.tree.length; g++) {
                        startIndex += updateGroup(this.tree[g].itemsContainer, startIndex);
                    }

                    that._blockSize = blockSize;
                },

                _layoutItems: function VirtualizeContentsView_layoutItems() {
                    var that = this;
                    return this._listView._itemsCount().then(function () {
                        return Promise.as(that._listView._layout.numberOfItemsPerItemsBlock).then(function (blockSize) {
                            that._listView._writeProfilerMark("numberOfItemsPerItemsBlock(" + blockSize + "),info");
                            if (blockSize !== that._blockSize) {
                                that._updateItemsBlocks(blockSize);
                                that._listView._itemsBlockExtent = -1;
                            }

                            var affectedRange = that._listView._affectedRange.get();
                            var changedRange;

                            // We accumulate all changes that occur between layouts in _affectedRange. If layout is interrupted due to additional
                            // modifications, _affectedRange will become the union of the previous range of changes and the new range of changes
                            // and will be passed to layout again. _affectedRange is reset whenever layout completes.
                            if (affectedRange) {
                                changedRange = {
                                    // _affectedRange is stored in the format [start, end), layout expects a range in the form of [firstIndex , lastIndex]
                                    // To ensure that layout can successfully use the expected range to find all of the groups which need to be re-laid out
                                    // we will pad an extra index at the front end such that layout receives [start - 1, end] in form of [lastIndex, firstIndex].
                                    firstIndex: Math.max(affectedRange.start - 1, 0),
                                    lastIndex: Math.min(that.containers.length - 1, affectedRange.end) // Account for any constrained upper limits from lazily loaded win-container's.
                                };
                                if (changedRange.firstIndex < that.containers.length || that.containers.length === 0) {
                                    return that._listView._layout.layout(that.tree, changedRange,
                                        that._modifiedElements || [], that._modifiedGroups || []);
                                }
                            }

                            // There is nothing to layout.
                            that._listView._affectedRange.clear();
                            return {
                                realizedRangeComplete: Promise.wrap(),
                                layoutComplete: Promise.wrap()
                            };
                        });
                    });
                },

                updateTree: function VirtualizeContentsView_updateTree(count, delta, modifiedElements) {
                    this._listView._writeProfilerMark(this._state.name + "_updateTree,info");
                    return this._state.updateTree(count, delta, modifiedElements);
                },

                _updateTreeImpl: function VirtualizeContentsView_updateTreeImpl(count, delta, modifiedElements, skipUnrealizeItems) {
                    this._executeAnimations = true;
                    this._modifiedElements = modifiedElements;

                    if (modifiedElements.handled) {
                        return;
                    }
                    modifiedElements.handled = true;

                    this._listView._writeProfilerMark("_updateTreeImpl,StartTM");

                    var that = this,
                        i;

                    if (!skipUnrealizeItems) {
                        // If we skip unrealize items, this work will eventually happen when we reach the UnrealizingState. Sometimes,
                        // it is appropriate to defer the unrealize work in order to optimize scenarios (e.g, edits that happen when we are
                        // in the CompletedState, that way the animation can start sooner).
                        this._unrealizeItems();
                    }

                    function removeElements(array) {
                        for (var i = 0, len = array.length; i < len; i++) {
                            var itemBox = array[i];
                            itemBox.parentNode.removeChild(itemBox);
                        }
                    }

                    for (var i = 0, len = modifiedElements.length; i < len; i++) {
                        if (modifiedElements[i]._itemBox && modifiedElements[i]._itemBox.parentNode) {
                            _ElementUtilities.removeClass(modifiedElements[i]._itemBox.parentNode, _Constants._selectedClass);
                        }
                    }

                    this.items.each(function (index, item, itemData) {
                        itemData.container && _ElementUtilities.removeClass(itemData.container, _Constants._selectedClass);
                        itemData.container && _ElementUtilities.addClass(itemData.container, _Constants._backdropClass);
                    });

                    var removedGroups = this._listView._updateContainers(this._getGroups(count), count, delta, modifiedElements);

                    removeElements(removedGroups.removedHeaders);
                    removeElements(removedGroups.removedItemsContainers);

                    for (var i = 0, len = modifiedElements.length; i < len; i++) {
                        var modifiedElement = modifiedElements[i];
                        if (modifiedElement.newIndex !== -1) {
                            modifiedElement.element = this.getContainer(modifiedElement.newIndex);
                            if (!modifiedElement.element) {
                                throw "Container missing after updateContainers.";
                            }
                        } else {
                            _ElementUtilities.removeClass(modifiedElement.element, _Constants._backdropClass);
                        }
                    }

                    // We only need to restore focus if the current focus is within surface
                    var activeElement = _Global.document.activeElement;
                    if (this._listView._canvas.contains(activeElement)) {
                        this._requireFocusRestore = activeElement;
                    }

                    this._deferredReparenting = [];
                    this.items.each(function (index, item, itemData) {
                        var container = that.getContainer(index),
                            itemBox = itemData.itemBox;

                        if (itemBox && container) {
                            if (itemBox.parentNode !== container) {
                                if (index >= that.firstIndexDisplayed && index <= that.lastIndexDisplayed) {
                                    that._appendAndRestoreFocus(container, itemBox);
                                } else {
                                    that._deferredReparenting.push({ itemBox: itemBox, container: container });
                                }
                            }
                            _ElementUtilities.removeClass(container, _Constants._backdropClass);
                            itemData.container = container;

                            _ElementUtilities[that._listView.selection._isIncluded(index) ? "addClass" : "removeClass"](container, _Constants._selectedClass);
                            if (!that._listView.selection._isIncluded(index) && _ElementUtilities.hasClass(itemBox, _Constants._selectedClass)) {
                                _ItemEventsHandler._ItemEventsHandler.renderSelection(itemBox, itemData.element, false, true);
                            }
                        }
                    });

                    this._listView._writeProfilerMark("_updateTreeImpl,StopTM");
                },

                _completeUpdateTree: function () {
                    if (this._deferredReparenting) {
                        var deferredCount = this._deferredReparenting.length;
                        if (deferredCount > 0) {
                            var perfId = "_completeReparenting(" + deferredCount + ")";
                            this._listView._writeProfilerMark(perfId + ",StartTM");
                            var deferredItem;
                            for (var i = 0; i < deferredCount; i++) {
                                deferredItem = this._deferredReparenting[i];
                                this._appendAndRestoreFocus(deferredItem.container, deferredItem.itemBox);
                            }
                            this._deferredReparenting = [];
                            this._listView._writeProfilerMark(perfId + ",StopTM");
                        }
                    }
                    this._requireFocusRestore = null;
                },

                _appendAndRestoreFocus: function VirtualizeContentsView_appendAndRestoreFocus(container, itemBox) {
                    if (itemBox.parentNode !== container) {
                        var activeElement;
                        if (this._requireFocusRestore) {
                            activeElement = _Global.document.activeElement;
                        }

                        if (this._requireFocusRestore && this._requireFocusRestore === activeElement && (container.contains(activeElement) || itemBox.contains(activeElement))) {
                            this._listView._unsetFocusOnItem();
                            activeElement = _Global.document.activeElement;
                        }

                        _ElementUtilities.empty(container);
                        container.appendChild(itemBox);

                        if (this._requireFocusRestore && activeElement === this._listView._keyboardEventsHelper) {
                            var focused = this._listView._selection._getFocused();
                            if (focused.type === _UI.ObjectType.item && this.items.itemBoxAt(focused.index) === itemBox) {
                                _ElementUtilities._setActive(this._requireFocusRestore);
                                this._requireFocusRestore = null;
                            }
                        }
                    }
                },

                _startAnimations: function VirtualizeContentsView_startAnimations() {
                    this._listView._writeProfilerMark("startAnimations,StartTM");

                    var that = this;
                    this._hasAnimationInViewportPending = false;
                    var animationPromise = Promise.as(this._listView._layout.executeAnimations()).then(function () {
                        that._listView._writeProfilerMark("startAnimations,StopTM");
                    });
                    return animationPromise;
                },

                _setState: function VirtualizeContentsView_setState(NewStateType, arg) {
                    if (!this._listView._isZombie()) {
                        var prevStateName = this._state.name;
                        this._state = new NewStateType(this, arg);
                        this._listView._writeProfilerMark(this._state.name + "_enter from(" + prevStateName + "),info");
                        this._state.enter();
                    }
                },

                getAdjacent: function VirtualizeContentsView_getAdjacent(currentFocus, direction) {
                    var that = this;
                    return this.waitForEntityPosition(currentFocus).then(function () {
                        return that._listView._layout.getAdjacent(currentFocus, direction);
                    });
                },

                hitTest: function VirtualizeContentsView_hitTest(x, y) {
                    if (!this._realizedRangeLaidOut) {
                        var retVal = this._listView._layout.hitTest(x, y);
                        retVal.index = _ElementUtilities._clamp(retVal.index, -1, this._listView._cachedCount - 1, 0);
                        retVal.insertAfterIndex = _ElementUtilities._clamp(retVal.insertAfterIndex, -1, this._listView._cachedCount - 1, 0);
                        return retVal;
                    } else {
                        return {
                            index: -1,
                            insertAfterIndex: -1
                        };
                    }
                },

                _createTreeBuildingSignal: function VirtualizeContentsView__createTreeBuildingSignal() {
                    if (!this._creatingContainersWork) {
                        this._creatingContainersWork = new _Signal();

                        var that = this;
                        this._creatingContainersWork.promise.done(
                            function () {
                                that._creatingContainersWork = null;
                            },
                            function () {
                                that._creatingContainersWork = null;
                            }
                        );
                    }
                },

                _createLayoutSignal: function VirtualizeContentsView_createLayoutSignal() {
                    var that = this;

                    if (!this._layoutCompleted) {
                        this._layoutCompleted = new _Signal();

                        this._layoutCompleted.promise.done(
                            function () {
                                that._layoutCompleted = null;
                            },
                            function () {
                                that._layoutCompleted = null;
                            }
                        );
                    }

                    if (!this._realizedRangeLaidOut) {
                        this._realizedRangeLaidOut = new _Signal();
                        this._realizedRangeLaidOut.promise.done(
                            function () {
                                that._realizedRangeLaidOut = null;
                            },
                            function () {
                                that._realizedRangeLaidOut = null;
                            }
                        );
                    }
                },

                _getLayoutCompleted: function VirtualizeContentsView_getLayoutCompleted() {
                    return this._layoutCompleted ? Promise._cancelBlocker(this._layoutCompleted.promise) : Promise.wrap();
                },

                _createSurfaceChild: function VirtualizeContentsView_createSurfaceChild(className, insertAfter) {
                    var element = _Global.document.createElement("div");
                    element.className = className;
                    this._listView._canvas.insertBefore(element, insertAfter ? insertAfter.nextElementSibling : null);
                    return element;
                },

                _executeScrollToFunctor: function VirtualizeContentsView_executeScrollToFunctor() {
                    var that = this;
                    return Promise.as(this._scrollToFunctor ? this._scrollToFunctor() : null).then(function (scroll) {
                        that._scrollToFunctor = null;

                        scroll = scroll || {};
                        // _scrollbarPos is initialized to 0 in the constructor, and we only set it when a valid integer
                        // value is passed in order to account for cases when there is not a _scrollToFunctor
                        if (+scroll.position === scroll.position) {
                            that._scrollbarPos = scroll.position;
                        }
                        that._direction = scroll.direction || "right";
                    });
                }
            },{
                _pagesToPrefetch: 2,
                _customPagesToPrefetchMax: 6,
                _customPagesToPrefetchMin: 2,
                _disableCustomPagesPrefetch: false,
                _waitForSeZoIntervalDuration: 100,
                _waitForSeZoTimeoutDuration: 500,
                _chunkSize: 500,
                _startupChunkSize: 100,
                _maxTimePerCreateContainers: 5,
                _createContainersJobTimeslice: 15,
                _blocksToRelease:10,
                _realizationLevel: {
                    skip: "skip",
                    realize: "realize",
                    normal: "normal"
                }
            });


            function nop() { }

            /*
            View is in this state before reload is called so during startup, after datasource change etc.
            */

            var CreatedState = _Base.Class.define(function CreatedState_ctor(view) {
                this.view = view;
                this.view._createTreeBuildingSignal();
                this.view._createLayoutSignal();
            }, {
                name: 'CreatedState',
                enter: function CreatedState_enter() {
                    this.view._createTreeBuildingSignal();
                    this.view._createLayoutSignal();
                },
                stop: nop,
                realizePage: nop,
                rebuildTree: function CreatedState_rebuildTree() {
                    this.view._setState(BuildingState);
                },
                relayout: function CreatedState_relayout() {
                    this.view._setState(BuildingState);
                },
                layoutNewContainers: nop,
                waitForEntityPosition: function CreatedState_waitForEntityPosition() {
                    this.view._setState(BuildingState);
                    return this.view._getLayoutCompleted();
                },
                updateTree: nop
            });

            /*
            In this state View is building its DOM tree with win-container element for each item in the data set.
            To build the tree the view needs to know items count or for grouped case the count of groups and the
            count of items in each group. The view enters this state when the tree needs to be built during
            startup or rebuild after data source change and etc.

            BuildingState => LayingoutState | CreatedState
            */
            var BuildingState = _Base.Class.define(function BuildingState_ctor(view) {
                this.view = view;
            }, {
                name: 'BuildingState',
                enter: function BuildingState_enter() {
                    this.canceling = false;
                    this.view._createTreeBuildingSignal();
                    this.view._createLayoutSignal();

                    var that = this;

                    // Use a signal to guarantee that this.promise is set before the promise
                    // handler is executed.
                    var promiseStoredSignal = new _Signal();
                    this.promise = promiseStoredSignal.promise.then(function () {
                        return that.view._createContainers();
                    }).then(
                        function () {
                            that.view._setState(LayingoutState);
                        },
                        function (error) {
                            if (!that.canceling) {
                                // this is coming from layout. ListView is hidden. We need to raise complete and wait in initial state for further actions
                                that.view._setState(CreatedState);
                                that.view._listView._raiseViewComplete();
                            }
                            return Promise.wrapError(error);
                        }
                    );
                    promiseStoredSignal.complete();
                },
                stop: function BuildingState_stop() {
                    this.canceling = true;
                    this.promise.cancel();
                    this.view._setState(CreatedState);
                },
                realizePage: nop,
                rebuildTree: function BuildingState_rebuildTree() {
                    this.canceling = true;
                    this.promise.cancel();
                    this.enter();
                },
                relayout: nop,
                layoutNewContainers: nop,
                waitForEntityPosition: function BuildingState_waitForEntityPosition() {
                    return this.view._getLayoutCompleted();
                },
                updateTree: nop
            });

            /*
            In this state View waits for the layout to lay out win-container elements. The view enters this state
            after edits or resize.

            LayingoutState => RealizingState | BuildingState | CanceledState | CompletedState | LayoutCanceledState
            */
            var LayingoutState = _Base.Class.define(function LayingoutState_ctor(view, NextStateType) {
                this.view = view;
                this.nextStateType = NextStateType || RealizingState;
            }, {
                name: 'LayingoutState',
                enter: function LayingoutState_enter() {
                    var that = this;
                    this.canceling = false;
                    this.view._createLayoutSignal();

                    this.view._listView._writeProfilerMark(this.name + "_enter_layoutItems,StartTM");

                    // Use a signal to guarantee that this.promise is set before the promise
                    // handler is executed.
                    var promiseStoredSignal = new _Signal();
                    this.promise = promiseStoredSignal.promise.then(function () {
                        return that.view._layoutItems();
                    }).then(function (layoutPromises) {

                        // View is taking ownership of this promise and it will cancel it in stopWork
                        that.view._layoutWork = layoutPromises.layoutComplete;

                        return layoutPromises.realizedRangeComplete;
                    }).then(
                        function () {
                            that.view._listView._writeProfilerMark(that.name + "_enter_layoutItems,StopTM");

                            that.view._listView._clearInsertedItems();
                            that.view._setAnimationInViewportState(that.view._modifiedElements);
                            that.view._modifiedElements = [];
                            that.view._modifiedGroups = [];

                            that.view._realizedRangeLaidOut.complete();

                            that.view._layoutWork.then(function () {
                                that.view._listView._writeProfilerMark(that.name + "_enter_layoutCompleted,info");
                                that.view._listView._affectedRange.clear();
                                that.view._layoutCompleted.complete();
                            });

                            if (!that.canceling) {
                                that.view._setState(that.nextStateType);
                            }
                        },
                        function (error) {
                            that.view._listView._writeProfilerMark(that.name + "_enter_layoutCanceled,info");

                            if (!that.canceling) {
                                // Cancel is coming from layout itself so ListView is hidden or empty. In this case we want to raise loadingStateChanged
                                that.view.firstIndexDisplayed = that.view.lastIndexDisplayed = -1;
                                that.view._updateAriaMarkers(true, that.view.firstIndexDisplayed, that.view.lastIndexDisplayed);
                                that.view._setState(CompletedState);
                            }

                            return Promise.wrapError(error);
                        }
                    );
                    promiseStoredSignal.complete();

                    if (this.canceling) {
                        this.promise.cancel();
                    }
                },
                cancelLayout: function LayingoutState_cancelLayout(switchState) {
                    this.view._listView._writeProfilerMark(this.name + "_cancelLayout,info");
                    this.canceling = true;
                    if (this.promise) {
                        this.promise.cancel();
                    }
                    if (switchState) {
                        this.view._setState(LayoutCanceledState);
                    }
                },
                stop: function LayingoutState_stop() {
                    this.cancelLayout(true);
                },
                realizePage: nop,
                rebuildTree: function LayingoutState_rebuildTree() {
                    this.cancelLayout(false);
                    this.view._setState(BuildingState);
                },
                relayout: function LayingoutState_relayout() {
                    this.cancelLayout(false);
                    this.enter();
                },
                layoutNewContainers: function LayingoutState_layoutNewContainers() {
                    this.relayout();
                },
                waitForEntityPosition: function LayingoutState_waitForEntityPosition() {
                    return this.view._getLayoutCompleted();
                },
                updateTree: function LayingoutState_updateTree(count, delta, modifiedElements) {
                    return this.view._updateTreeImpl(count, delta, modifiedElements);
                }
            });


            /*
            View enters this state when layout is canceled.

            LayoutCanceledState => LayingoutState | BuildingState
            */
            var LayoutCanceledState = _Base.Class.define(function LayoutCanceledState_ctor(view) {
                this.view = view;
            }, {
                name: 'LayoutCanceledState',
                enter: nop,
                stop: nop,
                realizePage: function LayoutCanceledState_realizePage() {
                    this.relayout();
                },
                rebuildTree: function LayoutCanceledState_rebuildTree() {
                    this.view._setState(BuildingState);
                },
                relayout: function LayoutCanceledState_relayout() {
                    this.view._setState(LayingoutState);
                },
                layoutNewContainers: function LayoutCanceledState_layoutNewContainers() {
                    this.relayout();
                },
                waitForEntityPosition: function LayoutCanceledState_waitForEntityPosition() {
                    return this.view._getLayoutCompleted();
                },
                updateTree: function LayoutCanceledState_updateTree(count, delta, modifiedElements) {
                    return this.view._updateTreeImpl(count, delta, modifiedElements);
                }
            });

            /*
            Contents of items in the current viewport and prefetch area is realized during this stage.
            The view enters this state when items needs to be realized for instance during initialization, edits and resize.

            RealizingState => RealizingAnimatingState | UnrealizingState | LayingoutState | BuildingState | CanceledState
            */
            var RealizingState = _Base.Class.define(function RealizingState_ctor(view) {
                this.view = view;
                this.nextState = UnrealizingState;
                this.relayoutNewContainers = true;
            }, {
                name: 'RealizingState',
                enter: function RealizingState_enter() {
                    var that = this;
                    var promiseStoredSignal = new _Signal();
                    this.promise = promiseStoredSignal.promise.then(function () {
                        return that.view._executeScrollToFunctor();
                    }).then(function () {
                        that.relayoutNewContainers = false;
                        return Promise._cancelBlocker(that.view._realizePageImpl());
                    }).then(
                        function () {
                            if (that.view._state === that) {
                                that.view._completeUpdateTree();
                                that.view._listView._writeProfilerMark("RealizingState_to_UnrealizingState");
                                that.view._setState(that.nextState);
                            }
                        },
                        function (error) {
                            if (that.view._state === that && !that.canceling) {
                                that.view._listView._writeProfilerMark("RealizingState_to_CanceledState");
                                that.view._setState(CanceledState);
                            }
                            return Promise.wrapError(error);
                        }
                    );
                    promiseStoredSignal.complete();
                },
                stop: function RealizingState_stop() {
                    this.canceling = true;
                    this.promise.cancel();
                    this.view._cancelRealize();
                    this.view._setState(CanceledState);
                },
                realizePage: function RealizingState_realizePage() {
                    this.canceling = true;
                    this.promise.cancel();
                    this.enter();
                },
                rebuildTree: function RealizingState_rebuildTree() {
                    this.stop();
                    this.view._setState(BuildingState);
                },
                relayout: function RealizingState_relayout() {
                    this.stop();
                    this.view._setState(LayingoutState);
                },
                layoutNewContainers: function RealizingState_layoutNewContainers() {
                    if (this.relayoutNewContainers) {
                        this.relayout();
                    } else {
                        this.view._createLayoutSignal();
                        this.view._relayoutInComplete = true;
                    }
                },
                waitForEntityPosition: function RealizingState_waitForEntityPosition() {
                    return this.view._getLayoutCompleted();
                },
                updateTree: function RealizingState_updateTree(count, delta, modifiedElements) {
                    return this.view._updateTreeImpl(count, delta, modifiedElements);
                },
                setLoadingState: function RealizingState_setLoadingState(state) {
                    this.view._listView._setViewState(state);
                }
            });

            /*
            The view enters this state when the realize pass, animations or unrealizing was canceled or after newContainers have been laid out.
            In this state view waits for the next call from ListViewImpl. It can be scroll, edit etc.

            CanceledState => RealizingState | ScrollingState | LayingoutState | BuildingState
            */
            var CanceledState = _Base.Class.define(function CanceledState_ctor(view) {
                this.view = view;
            }, {
                name: 'CanceledState',
                enter: nop,
                stop: function CanceledState_stop() {
                    // cancelRealize cancels ariaSetup which can still be in progress
                    this.view._cancelRealize();
                },
                realizePage: function CanceledState_realizePage(NewStateType) {
                    this.stop();
                    this.view._setState(NewStateType);
                },
                rebuildTree: function CanceledState_rebuildTree() {
                    this.stop();
                    this.view._setState(BuildingState);
                },
                relayout: function CanceledState_relayout(NextStateType) {
                    this.stop();
                    this.view._setState(LayingoutState, NextStateType);
                },
                layoutNewContainers: function CanceledState_layoutNewContainers() {
                    this.relayout(CanceledState);
                },
                waitForEntityPosition: function CanceledState_waitForEntityPosition() {
                    return this.view._getLayoutCompleted();
                },
                updateTree: function CanceledState_updateTree(count, delta, modifiedElements) {
                    return this.view._updateTreeImpl(count, delta, modifiedElements);
                }
            });

            /*
            This state is almost identical with RealizingState. Currently the difference is that in this state loadingStateChanged events aren’t
            raised and after complete the state is switched to ScrollingPausedState to wait until end of scrolling.

            ScrollingState => RealizingAnimatingState | ScrollingPausedState | LayingoutState | BuildingState | CanceledState
            */
            var ScrollingState = _Base.Class.derive(RealizingState, function ScrollingState_ctor(view) {
                this.view = view;
                this.nextState = ScrollingPausedState;
                this.relayoutNewContainers = true;
            }, {
                name: 'ScrollingState',
                setLoadingState: function ScrollingState_setLoadingState() {
                }
            });

            /*
            The view waits in this state for end of scrolling which for touch is signaled by MSManipulationStateChanged event and for mouse it is timeout.

            ScrollingPausedState => RealizingAnimatingState | ScrollingPausedState | LayingoutState | BuildingState | CanceledState
            */
            var ScrollingPausedState = _Base.Class.derive(CanceledState, function ScrollingPausedState_ctor(view) {
                this.view = view;
            }, {
                name: 'ScrollingPausedState',
                enter: function ScrollingPausedState_enter() {
                    var that = this;
                    this.promise = Promise._cancelBlocker(this.view._scrollEndPromise).then(function () {
                        that.view._setState(UnrealizingState);
                    });
                },
                stop: function ScrollingPausedState_stop() {
                    this.promise.cancel();
                    // cancelRealize cancels ariaSetup which can still be in progress
                    this.view._cancelRealize();
                },
            });

            /*
            In this state, view unrealizes not needed items and then waits for all renderers to complete.

            UnrealizingState => CompletedState | RealizingState | ScrollingState | LayingoutState | BuildingState | CanceledState
            */
            var UnrealizingState = _Base.Class.define(function UnrealizingState_ctor(view) {
                this.view = view;
            }, {
                name: 'UnrealizingState',
                enter: function UnrealizingState_enter() {
                    var that = this;
                    this.promise = this.view._lazilyUnrealizeItems().then(function () {
                        that.view._listView._writeProfilerMark("_renderCompletePromise wait starts,info");
                        return that.view._renderCompletePromise;
                    }).then(function () {
                        that.view._setState(CompletedState);
                    });
                },
                stop: function UnrealizingState_stop() {
                    // cancelRealize cancels ariaSetup which can still be in progress
                    this.view._cancelRealize();
                    this.promise.cancel();
                    this.view._setState(CanceledState);
                },
                realizePage: function UnrealizingState_realizePage(NewStateType) {
                    this.promise.cancel();
                    this.view._setState(NewStateType);
                },
                rebuildTree: function UnrealizingState_rebuildTree() {
                    this.view._setState(BuildingState);
                },
                relayout: function UnrealizingState_relayout() {
                    this.view._setState(LayingoutState);
                },
                layoutNewContainers: function UnrealizingState_layoutNewContainers() {
                    this.view._createLayoutSignal();
                    this.view._relayoutInComplete = true;
                },
                waitForEntityPosition: function UnrealizingState_waitForEntityPosition() {
                    return this.view._getLayoutCompleted();
                },
                updateTree: function UnrealizingState_updateTree(count, delta, modifiedElements) {
                    return this.view._updateTreeImpl(count, delta, modifiedElements);
                }
            });

            /*
            We enter this state, when there are animations to execute, and we have already fired the viewportloaded event

            RealizingAnimatingState => RealizingState | UnrealizingState | LayingoutState | BuildingState | CanceledState
            */
            var RealizingAnimatingState = _Base.Class.define(function RealizingStateAnimating_ctor(view, realizePromise) {
                this.view = view;
                this.realizePromise = realizePromise;
                this.realizeId = 1;
            }, {
                name: 'RealizingAnimatingState',
                enter: function RealizingAnimatingState_enter() {
                    var that = this;


                    this.animating = true;
                    this.animatePromise = this.view._startAnimations();
                    this.animateSignal = new _Signal();
                    this.view._executeAnimations = false;

                    this.animatePromise.done(
                        function () {
                            that.animating = false;
                            if (that.modifiedElements) {
                                that.view._updateTreeImpl(that.count, that.delta, that.modifiedElements);
                                that.modifiedElements = null;
                                that.view._setState(CanceledState);
                            } else {
                                that.animateSignal.complete();
                            }
                        }, function (error) {
                            that.animating = false;
                            return Promise.wrapError(error);
                        }
                    );

                    this._waitForRealize();
                },

                _waitForRealize: function RealizingAnimatingState_waitForRealize() {
                    var that = this;

                    this.realizing = true;
                    this.realizePromise.done(function () {
                        that.realizing = false;
                    });

                    var currentRealizeId = ++this.realizeId;
                    Promise.join([this.realizePromise, this.animateSignal.promise]).done(function () {
                        if (currentRealizeId === that.realizeId) {
                            that.view._completeUpdateTree();
                            that.view._listView._writeProfilerMark("RealizingAnimatingState_to_UnrealizingState");
                            that.view._setState(UnrealizingState);
                        }
                    });
                },

                stop: function RealizingAnimatingState_stop(stopTreeCreation) {
                    // always cancel realization
                    this.realizePromise.cancel();
                    this.view._cancelRealize();

                    // animations are canceled only when tree needs to be rebuilt
                    if (stopTreeCreation) {
                        this.animatePromise.cancel();
                        this.view._setState(CanceledState);
                    }
                },
                realizePage: function RealizingAnimatingState_realizePage() {
                    if (!this.modifiedElements) {
                        var that = this;
                        this.realizePromise = this.view._executeScrollToFunctor().then(function () {
                            return Promise._cancelBlocker(that.view._realizePageImpl());
                        });
                        this._waitForRealize();
                    }
                },
                rebuildTree: function RealizingAnimatingState_rebuildTree() {
                    this.stop(true);
                    this.view._setState(BuildingState);
                },
                relayout: function RealizingAnimatingState_relayout() {
                    // Relayout caused by edits should be stopped by updateTree but relayout can be caused by resize or containers creation and in these cases we should stop animations
                    this.stop(true);
                    // if tree update was waiting for animations we should do it now
                    if (this.modifiedElements) {
                        this.view._updateTreeImpl(this.count, this.delta, this.modifiedElements);
                        this.modifiedElements = null;
                    }
                    this.view._setState(LayingoutState);
                },
                layoutNewContainers: function RealizingAnimatingState_layoutNewContainers() {
                    this.view._createLayoutSignal();
                    this.view._relayoutInComplete = true;
                },
                waitForEntityPosition: function RealizingAnimatingState_waitForEntityPosition() {
                    return this.view._getLayoutCompleted();
                },
                updateTree: function RealizingAnimatingState_updateTree(count, delta, modifiedElements) {
                    if (this.animating) {
                        var previousModifiedElements = this.modifiedElements;
                        this.count = count;
                        this.delta = delta;
                        this.modifiedElements = modifiedElements;

                        return previousModifiedElements ? Promise.cancel : this.animatePromise;
                    } else {
                        return this.view._updateTreeImpl(count, delta, modifiedElements);
                    }
                },
                setLoadingState: function RealizingAnimatingState_setLoadingState(state) {
                    this.view._listView._setViewState(state);
                }
            });

            /*
            The view enters this state when the tree is built, layout and realized after animations have
            finished. The layout can still laying out items outside of realized view during this stage.

            CompletedState => RealizingState | ScrollingState | LayingoutState | BuildingState | LayingoutNewContainersState
            */
            var CompletedState = _Base.Class.derive(CanceledState, function CompletedState_ctor(view) {
                this.view = view;
            }, {
                name: 'CompletedState',
                enter: function CompletedState_enter() {
                    this._stopped = false;
                    this.view._setupDeferredActions();

                    this.view._realizationLevel = _VirtualizeContentsView._realizationLevel.normal;
                    this.view._listView._raiseViewComplete();

                    // _raiseViewComplete will cause user event listener code to run synchronously.
                    // If any updates are made to the Listview, this state will be stopped by the updater.
                    // We don't want to change state to LayingoutNewContainersState if that happens.
                    if (this.view._state === this && this.view._relayoutInComplete && !this._stopped) {
                        this.view._setState(LayingoutNewContainersState);
                    }
                },
                stop: function CompletedState_stop() {
                    this._stopped = true;
                    // Call base class method.
                    CanceledState.prototype.stop.call(this);
                },
                layoutNewContainers: function CompletedState_layoutNewContainers() {
                    this.view._createLayoutSignal();
                    this.view._setState(LayingoutNewContainersState);
                },
                updateTree: function CompletedState_updateTree(count, delta, modifiedElements) {
                    return this.view._updateTreeImpl(count, delta, modifiedElements, true);
                }
            });

            /*
            The view waits in this state for previous layout pass to finish.

            LayingoutNewContainersState => RealizingState | ScrollingState | LayingoutState | BuildingState
            */
            var LayingoutNewContainersState = _Base.Class.derive(CanceledState, function LayingoutNewContainersState(view) {
                this.view = view;
            }, {
                name: 'LayingoutNewContainersState',
                enter: function LayingoutNewContainersState_enter() {
                    var that = this;

                    // _layoutWork is completed when the previous layout pass is done. _getLayoutCompleted will be completed when these new containers are laid out
                    this.promise = Promise.join([this.view.deferTimeout, this.view._layoutWork]);
                    this.promise.then(function () {
                        that.view._relayoutInComplete = false;
                        that.relayout(CanceledState);
                    });
                },
                stop: function LayingoutNewContainersState_stop() {
                    // cancelRealize cancels ariaSetup which can still be in progress
                    this.promise.cancel();
                    this.view._cancelRealize();
                },
                realizePage: function LayingoutNewContainersState_realizePage(NewStateType) {
                    // in this state realizePage needs to run layout before realizing items
                    this.stop();
                    this.view._setState(LayingoutState, NewStateType);
                },
                layoutNewContainers: function LayingoutNewContainersState_layoutNewContainers() {
                    this.view._createLayoutSignal();
                }
            });

            return _VirtualizeContentsView;
        })
    });

});


define('require-style!less/desktop/controls',[],function(){});

define('require-style!less/phone/controls',[],function(){});
// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/ListView',[
    '../Core/_Global',
    '../Core/_Base',
    '../Core/_BaseUtils',
    '../Core/_ErrorFromName',
    '../Core/_Events',
    '../Core/_Log',
    '../Core/_Resources',
    '../Core/_WriteProfilerMark',
    '../Animations/_TransitionAnimation',
    '../BindingList',
    '../Promise',
    '../Scheduler',
    '../_Signal',
    '../Utilities/_Control',
    '../Utilities/_Dispose',
    '../Utilities/_ElementUtilities',
    '../Utilities/_Hoverable',
    '../Utilities/_ItemsManager',
    '../Utilities/_SafeHtml',
    '../Utilities/_TabContainer',
    '../Utilities/_UI',
    '../Utilities/_VersionManager',
    './ItemContainer/_Constants',
    './ItemContainer/_ItemEventsHandler',
    './ListView/_BrowseMode',
    './ListView/_ErrorMessages',
    './ListView/_GroupFocusCache',
    './ListView/_GroupsContainer',
    './ListView/_Helpers',
    './ListView/_ItemsContainer',
    './ListView/_Layouts',
    './ListView/_SelectionManager',
    './ListView/_VirtualizeContentsView',
    'require-style!less/desktop/controls',
    'require-style!less/phone/controls'
    ], function listViewImplInit(_Global, _Base, _BaseUtils, _ErrorFromName, _Events, _Log, _Resources, _WriteProfilerMark, _TransitionAnimation, BindingList, Promise, Scheduler, _Signal, _Control, _Dispose, _ElementUtilities, _Hoverable, _ItemsManager, _SafeHtml, _TabContainer, _UI, _VersionManager, _Constants, _ItemEventsHandler, _BrowseMode, _ErrorMessages, _GroupFocusCache, _GroupsContainer, _Helpers, _ItemsContainer, _Layouts, _SelectionManager, _VirtualizeContentsView) {
    "use strict";

    var transformNames = _BaseUtils._browserStyleEquivalents["transform"];
    var DISPOSE_TIMEOUT = 1000;
    var controlsToDispose = [];
    var disposeControlTimeout;
    var uniqueID = _ElementUtilities._uniqueID;

    function disposeControls() {
        var temp = controlsToDispose;
        controlsToDispose = [];
        temp = temp.filter(function (c) {
            if (c._isZombie()) {
                c._dispose();
                return false;
            } else {
                return true;
            }
        });
        controlsToDispose = controlsToDispose.concat(temp);
    }
    function scheduleForDispose(lv) {
        controlsToDispose.push(lv);
        disposeControlTimeout && disposeControlTimeout.cancel();
        disposeControlTimeout = Promise.timeout(DISPOSE_TIMEOUT).then(disposeControls);
    }

    function getOffsetRight(element) {
        return element.offsetParent ? (element.offsetParent.offsetWidth - element.offsetLeft - element.offsetWidth) : 0;
    }

    var AnimationHelper = _Helpers._ListViewAnimationHelper;

    var strings = {
        get notCompatibleWithSemanticZoom() { return "ListView can only be used with SemanticZoom if randomAccess loading behavior is specified."; },
        get listViewInvalidItem() { return "Item must provide index, key or description of corresponding item."; },
        get listViewViewportAriaLabel() { return _Resources._getWinJSString("ui/listViewViewportAriaLabel").value; }
    };

    var requireSupportedForProcessing = _BaseUtils.requireSupportedForProcessing;

    var ListViewAnimationType = {
        /// <field locid="WinJS.UI.ListView.ListViewAnimationType.entrance" helpKeyword="WinJS.UI.ListViewAnimationType.entrance">
        /// The animation plays when the ListView is first displayed.
        /// </field>
        entrance: "entrance",
        /// <field locid="WinJS.UI.ListView.ListViewAnimationType.contentTransition" helpKeyword="WinJS.UI.ListViewAnimationType.contentTransition">
        /// The animation plays when the ListView is changing its content.
        /// </field>
        contentTransition: "contentTransition"
    };

    // ListView implementation

    _Base.Namespace.define("WinJS.UI", {

        /// <field locid="WinJS.UI.ListView.ListViewAnimationType" helpKeyword="WinJS.UI.ListViewAnimationType">
        /// Specifies whether the ListView animation is an entrance animation or a transition animation.
        /// <compatibleWith platform="Windows" minVersion="8.0"/>
        /// </field>
        ListViewAnimationType: ListViewAnimationType,

        /// <field>
        /// <summary locid="WinJS.UI.ListView">
        /// Displays items in a customizable list or grid.
        /// </summary>
        /// </field>
        /// <icon src="ui_winjs.ui.listview.12x12.png" width="12" height="12" />
        /// <icon src="ui_winjs.ui.listview.16x16.png" width="16" height="16" />
        /// <htmlSnippet><![CDATA[<div data-win-control="WinJS.UI.ListView"></div>]]></htmlSnippet>
        /// <event name="contentanimating" bubbles="true" locid="WinJS.UI.ListView_e:contentanimating">Raised when the ListView is about to play an entrance or a transition animation.</event>
        /// <event name="iteminvoked" bubbles="true" locid="WinJS.UI.ListView_e:iteminvoked">Raised when the user taps or clicks an item.</event>
        /// <event name="groupheaderinvoked" bubbles="true" locid="WinJS.UI.ListView_e:groupheaderinvoked">Raised when the user taps or clicks a group header.</event>
        /// <event name="selectionchanging" bubbles="true" locid="WinJS.UI.ListView_e:selectionchanging">Raised before items are selected or deselected.</event>
        /// <event name="selectionchanged" bubbles="true" locid="WinJS.UI.ListView_e:selectionchanged">Raised after items are selected or deselected.</event>
        /// <event name="loadingstatechanged" bubbles="true" locid="WinJS.UI.ListView_e:loadingstatechanged">Raised when the loading state changes.</event>
        /// <event name="keyboardnavigating" bubbles="true" locid="WinJS.UI.ListView_e:keyboardnavigating">Raised when the focused item changes.</event>
        /// <event name="itemdragstart" bubbles="true" locid="WinJS.UI.ListView_e:itemdragstart">Raised when the the user begins dragging ListView items.</event>
        /// <event name="itemdragenter" bubbles="true" locid="WinJS.UI.ListView_e:itemdragenter">Raised when the user drags into the ListView.</event>
        /// <event name="itemdragend" bubbles="true" locid="WinJS.UI.ListView_e:itemdragend">Raised when a drag operation begun in a ListView ends.</event>
        /// <event name="itemdragbetween" bubbles="true" locid="WinJS.UI.ListView_e:itemdragbetween">Raised when the user drags between two ListView items.</event>
        /// <event name="itemdragleave" bubbles="true" locid="WinJS.UI.ListView_e:itemdragleave">Raised when the user drags outside of the ListView region.</event>
        /// <event name="itemdragchanged" bubbles="true" locid="WinJS.UI.ListView_e:itemdragchanged">Raised when the items being dragged are changed due to a datasource modification.</event>
        /// <event name="itemdragdrop" bubbles="true" locid="WinJS.UI.ListView_e:itemdragdrop">Raised when the user drops items into the ListView.</event>
        /// <event name="accessibilityannotationcomplete" bubbles="true" locid="WinJS.UI.ListView_e:accessibilityannotationcomplete">Raised when the accessibility attributes have been added to the ListView items.</event>
        /// <part name="listView" class="win-listview" locid="WinJS.UI.ListView_part:listView">The entire ListView control.</part>
        /// <part name="viewport" class="win-viewport" locid="WinJS.UI.ListView_part:viewport">The viewport of the ListView. </part>
        /// <part name="surface" class="win-surface" locid="WinJS.UI.ListView_part:surface">The scrollable region of the ListView.</part>
        /// <part name="item" class="win-item" locid="WinJS.UI.ListView_part:item">An item in the ListView.</part>
        /// <part name="selectionbackground" class="win-selectionbackground" locid="WinJS.UI.ListView_part:selectionbackground">The background of a selection checkmark.</part>
        /// <part name="selectioncheckmark" class="win-selectioncheckmark" locid="WinJS.UI.ListView_part:selectioncheckmark">A selection checkmark.</part>
        /// <part name="groupHeader" class="win-groupheader" locid="WinJS.UI.ListView_part:groupHeader">The header of a group.</part>
        /// <part name="progressbar" class="win-progress" locid="WinJS.UI.ListView_part:progressbar">The progress indicator of the ListView.</part>
        /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
        /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
        /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
        ListView: _Base.Namespace._lazy(function () {
            var AffectedRange = _Base.Class.define(function () {
                this.clear();
            }, {
                // Marks the union of the current affected range and range as requiring layout
                add: function (range, itemsCount) {
                    range._lastKnownSizeOfData = itemsCount; // store this in order to make unions.
                    if (!this._range) {
                        this._range = range;
                    } else {
                        // Take the union of these two ranges.
                        this._range.start = Math.min(this._range.start, range.start);
                        // To accurately calculate the new unioned range 'end' value, we need to convert the current and new range end
                        // positions into values that represent the remaining number of un-modified items in between the end of the range
                        // and the end of the list of data.
                        var previousUnmodifiedItemsFromEnd = (this._range._lastKnownSizeOfData - this._range.end);
                        var newUnmodifiedItemsFromEnd = (range._lastKnownSizeOfData - range.end);
                        var finalUnmodifiedItemsFromEnd = Math.min(previousUnmodifiedItemsFromEnd, newUnmodifiedItemsFromEnd);
                        this._range._lastKnownSizeOfData = range._lastKnownSizeOfData;
                        // Convert representation of the unioned end position back into a value which matches the above definition of _affecteRange.end
                        this._range.end = this._range._lastKnownSizeOfData - finalUnmodifiedItemsFromEnd;
                    }
                },

                // Marks everything as requiring layout
                addAll: function () {
                    this.add({ start: 0, end: Number.MAX_VALUE }, Number.MAX_VALUE);
                },

                // Marks nothing as requiring layout
                clear: function () {
                    this._range = null;
                },

                get: function () {
                    return this._range;
                }
            });

            var ZoomableView = _Base.Class.define(function ZoomableView_ctor(listView) {
                // Constructor

                this._listView = listView;
            }, {
                // Public methods

                getPanAxis: function () {
                    return this._listView._getPanAxis();
                },

                configureForZoom: function (isZoomedOut, isCurrentView, triggerZoom, prefetchedPages) {
                    this._listView._configureForZoom(isZoomedOut, isCurrentView, triggerZoom, prefetchedPages);
                },

                setCurrentItem: function (x, y) {
                    this._listView._setCurrentItem(x, y);
                },

                getCurrentItem: function () {
                    return this._listView._getCurrentItem();
                },

                beginZoom: function () {
                    return this._listView._beginZoom();
                },

                positionItem: function (item, position) {
                    return this._listView._positionItem(item, position);
                },

                endZoom: function (isCurrentView) {
                    this._listView._endZoom(isCurrentView);
                },

                pinching: {
                    get: function () {
                        return this._listView._pinching;
                    },
                    set: function (value) {
                        this._listView._pinching = value;
                    }
                }
            });

            var ListView = _Base.Class.define(function ListView_ctor(element, options) {
                /// <signature helpKeyword="WinJS.UI.ListView.ListView">
                /// <summary locid="WinJS.UI.ListView.constructor">
                /// Creates a new ListView.
                /// </summary>
                /// <param name="element" domElement="true" locid="WinJS.UI.ListView.constructor_p:element">
                /// The DOM element that hosts the ListView control.
                /// </param>
                /// <param name="options" type="Object" locid="WinJS.UI.ListView.constructor_p:options">
                /// An object that contains one or more property/value pairs to apply to the new control.
                /// Each property of the options object corresponds to one of the control's properties or events.
                /// Event names must begin with "on". For example, to provide a handler for the selectionchanged event,
                /// add a property named "onselectionchanged" to the options object and set its value to the event handler.
                /// </param>
                /// <returns type="WinJS.UI.ListView" locid="WinJS.UI.ListView.constructor_returnValue">
                /// The new ListView.
                /// </returns>
                /// </signature>
                element = element || _Global.document.createElement("div");
                if (_ElementUtilities._supportsTouchActionCrossSlide) {
                    element.classList.add(_Constants._listViewSupportsCrossSlideClass);
                }

                this._id = element.id || "";
                this._writeProfilerMark("constructor,StartTM");

                options = options || {};

                // Attaching JS control to DOM element
                element.winControl = this;
                _ElementUtilities.addClass(element, "win-disposable");
                this._affectedRange = new AffectedRange();
                this._mutationObserver = new _ElementUtilities._MutationObserver(this._itemPropertyChange.bind(this));
                this._versionManager = null;
                this._insertedItems = {};
                this._element = element;
                this._startProperty = null;
                this._scrollProperty = null;
                this._scrollLength = null;
                this._scrolling = false;
                this._zooming = false;
                this._pinching = false;
                this._itemsManager = null;
                this._canvas = null;
                this._cachedCount = _Constants._UNINITIALIZED;
                this._loadingState = this._LoadingState.complete;
                this._firstTimeDisplayed = true;
                this._currentScrollPosition = 0;
                this._lastScrollPosition = 0;
                this._notificationHandlers = [];
                this._itemsBlockExtent = -1;
                this._viewportWidth = _Constants._UNINITIALIZED;
                this._viewportHeight = _Constants._UNINITIALIZED;
                this._manipulationState = _ElementUtilities._MSManipulationEvent.MS_MANIPULATION_STATE_STOPPED;
                this._maxDeferredItemCleanup = Number.MAX_VALUE;
                this._groupsToRemove = {};
                this._setupInternalTree();
                this._isCurrentZoomView = true;
                this._dragSource = false;
                this._reorderable = false;
                this._groupFocusCache = new _GroupFocusCache._UnsupportedGroupFocusCache();
                this._viewChange = _Constants._ViewChange.rebuild;
                this._scrollToFunctor = null;
                this._setScrollbarPosition = false;
                // The view needs to be initialized after the internal tree is setup, because the view uses the canvas node immediately to insert an element in its constructor
                this._view = new _VirtualizeContentsView._VirtualizeContentsView(this);
                this._selection = new _SelectionManager._SelectionManager(this);
                this._createTemplates();
                this._groupHeaderRenderer = _ItemsManager._trivialHtmlRenderer;
                this._itemRenderer = _ItemsManager._trivialHtmlRenderer;
                this._groupHeaderRelease = null;
                this._itemRelease = null;
                if (!options.itemDataSource) {
                    var list = new BindingList.List();
                    this._dataSource = list.dataSource;
                } else {
                    this._dataSource = options.itemDataSource;
                }
                this._selectionMode = _UI.SelectionMode.multi;
                this._tap = _UI.TapBehavior.invokeOnly;
                this._groupHeaderTap = _UI.GroupHeaderTapBehavior.invoke;
                this._swipeBehavior = _UI.SwipeBehavior.select;
                this._mode = new _BrowseMode._SelectionMode(this);

                // Call after swipeBehavior and modes are set
                this._setSwipeClass();

                this._groups = new _GroupsContainer._NoGroups(this);
                this._updateItemsAriaRoles();
                this._updateGroupHeadersAriaRoles();
                this._element.setAttribute("aria-multiselectable", this._multiSelection());
                this._element.tabIndex = -1;
                this._tabManager.tabIndex = this._tabIndex;
                if (this._element.style.position !== "absolute" && this._element.style.position !== "relative") {
                    this._element.style.position = "relative";
                }
                this._updateItemsManager();
                if (!options.layout) {
                    this._updateLayout(new _Layouts.GridLayout());
                }
                this._attachEvents();

                this._runningInit = true;
                _Control.setOptions(this, options);
                this._runningInit = false;

                this._batchViewUpdates(_Constants._ViewChange.rebuild, _Constants._ScrollToPriority.medium, 0);
                this._writeProfilerMark("constructor,StopTM");
            }, {
                // Public properties

                /// <field type="HTMLElement" domElement="true" hidden="true" locid="WinJS.UI.ListView.element" helpKeyword="WinJS.UI.ListView.element">
                /// Gets the DOM element that hosts the ListView.
                /// </field>
                element: {
                    get: function () { return this._element; }
                },

                /// <field type="WinJS.UI.Layout" locid="WinJS.UI.ListView.layout" helpKeyword="WinJS.UI.ListView.layout">
                /// Gets or sets an object that controls the layout of the ListView.
                /// </field>
                layout: {
                    get: function () {
                        return this._layoutImpl;
                    },
                    set: function (layoutObject) {
                        this._updateLayout(layoutObject);

                        if (!this._runningInit) {
                            this._view.reset();
                            this._updateItemsManager();
                            this._batchViewUpdates(_Constants._ViewChange.rebuild, _Constants._ScrollToPriority.medium, 0, true);
                        }
                    }
                },

                /// <field type="Number" integer="true" locid="WinJS.UI.ListView.pagesToLoad" helpKeyword="WinJS.UI.ListView.pagesToLoad" isAdvanced="true">
                /// Gets or sets the number of pages to load when the user scrolls beyond the
                /// threshold specified by the pagesToLoadThreshold property if
                /// the loadingBehavior property is set to incremental.
                /// <deprecated type="deprecate">
                /// pagesToLoad is deprecated. The control will not use this property. Please refer to the 'ListView loading behaviors' SDK Sample for guidance on how to implement incremental load behavior.
                /// </deprecated>
                /// </field>
                pagesToLoad: {
                    get: function () {
                        return (_VirtualizeContentsView._VirtualizeContentsView._pagesToPrefetch * 2) + 1;
                    },
                    set: function () {
                        _ElementUtilities._deprecated(_ErrorMessages.pagesToLoadIsDeprecated);
                    }
                },

                /// <field type="Number" integer="true" locid="WinJS.UI.ListView.pagesToLoadThreshold" helpKeyword="WinJS.UI.ListView.pagesToLoadThreshold" isAdvanced="true">
                /// Gets or sets the threshold (in pages) for initiating an incremental load. When the last visible item is
                /// within the specified number of pages from the end of the loaded portion of the list,
                /// and if automaticallyLoadPages is true and loadingBehavior is set to "incremental",
                /// the ListView initiates an incremental load.
                /// <deprecated type="deprecate">
                /// pagesToLoadThreshold is deprecated.  The control will not use this property. Please refer to the 'ListView loading behaviors' SDK Sample for guidance on how to implement incremental load behavior.
                /// </deprecated>
                /// </field>
                pagesToLoadThreshold: {
                    get: function () {
                        return 0;
                    },
                    set: function () {
                        _ElementUtilities._deprecated(_ErrorMessages.pagesToLoadThresholdIsDeprecated);
                    }
                },

                /// <field type="Object" locid="WinJS.UI.ListView.groupDataSource" helpKeyword="WinJS.UI.ListView.groupDataSource">
                /// Gets or sets the data source that contains the groups for the items in the itemDataSource.
                /// </field>
                groupDataSource: {
                    get: function () {
                        return this._groupDataSource;
                    },
                    set: function (newValue) {
                        this._writeProfilerMark("set_groupDataSource,info");

                        var that = this;

                        function groupStatusChanged(eventObject) {
                            if (eventObject.detail === _UI.DataSourceStatus.failure) {
                                that.itemDataSource = null;
                                that.groupDataSource = null;
                            }
                        }

                        if (this._groupDataSource && this._groupDataSource.removeEventListener) {
                            this._groupDataSource.removeEventListener("statuschanged", groupStatusChanged, false);
                        }

                        this._groupDataSource = newValue;
                        this._groupFocusCache = (newValue && this._supportsGroupHeaderKeyboarding) ? new _GroupFocusCache._GroupFocusCache(this) : new _GroupFocusCache._UnsupportedGroupFocusCache();

                        if (this._groupDataSource && this._groupDataSource.addEventListener) {
                            this._groupDataSource.addEventListener("statuschanged", groupStatusChanged, false);
                        }

                        this._createGroupsContainer();

                        if (!this._runningInit) {
                            this._view.reset();
                            this._pendingLayoutReset = true;
                            this._pendingGroupWork = true;
                            this._batchViewUpdates(_Constants._ViewChange.rebuild, _Constants._ScrollToPriority.medium, 0, true);
                        } else {
                            this._updateGroupWork();
                            this._resetLayout();
                        }
                    }
                },

                _updateGroupWork: function () {
                    this._pendingGroupWork = false;

                    if (this._groupDataSource) {
                        _ElementUtilities.addClass(this._element, _Constants._groupsClass);
                    } else {
                        _ElementUtilities.removeClass(this._element, _Constants._groupsClass);
                    }
                    this._resetLayout();
                },

                /// <field type="Boolean" locid="WinJS.UI.ListView.automaticallyLoadPages" helpKeyword="WinJS.UI.ListView.automaticallyLoadPages">
                /// Gets or sets a value that indicates whether the next set of pages is automatically loaded
                /// when the user scrolls beyond the number of pages specified by the
                /// pagesToLoadThreshold property.
                /// <deprecated type="deprecate">
                /// automaticallyLoadPages is deprecated. The control will default this property to false. Please refer to the 'ListView loading behaviors' SDK Sample for guidance on how to implement incremental load behavior.
                /// </deprecated>
                /// </field>
                automaticallyLoadPages: {
                    get: function () {
                        return false;
                    },
                    set: function () {
                        _ElementUtilities._deprecated(_ErrorMessages.automaticallyLoadPagesIsDeprecated);
                    }
                },

                /// <field type="String" oamOptionsDatatype="WinJS.UI.ListView.LoadingBehavior" locid="WinJS.UI.ListView.loadingBehavior" helpKeyword="WinJS.UI.ListView.loadingBehavior">
                /// Gets or sets a value that determines how many data items are loaded into the DOM.
                /// <deprecated type="deprecate">
                /// pagesToLoadThreshold is deprecated. The control will default this property to 'randomAccess'. Please refer to the 'ListView loading behaviors' SDK Sample for guidance on how to implement incremental load behavior.
                /// </deprecated>
                /// </field>
                loadingBehavior: {
                    get: function () {
                        return "randomAccess";
                    },
                    set: function () {
                        _ElementUtilities._deprecated(_ErrorMessages.loadingBehaviorIsDeprecated);
                    }
                },

                /// <field type="String" oamOptionsDatatype="WinJS.UI.ListView.SelectionMode" locid="WinJS.UI.ListView.selectionMode" helpKeyword="WinJS.UI.ListView.selectionMode">
                /// Gets or sets a value that specifies how many ListView items the user can select: "none", "single", or "multi".
                /// </field>
                selectionMode: {
                    get: function () {
                        return this._selectionMode;
                    },
                    set: function (newMode) {
                        if (typeof newMode === "string") {
                            if (newMode.match(/^(none|single|multi)$/)) {
                                if (_BaseUtils.isPhone && newMode === _UI.SelectionMode.single) {
                                    return;
                                }
                                this._selectionMode = newMode;
                                this._element.setAttribute("aria-multiselectable", this._multiSelection());
                                this._updateItemsAriaRoles();
                                this._setSwipeClass();
                                this._configureSelectionMode();
                                return;
                            }
                        }
                        throw new _ErrorFromName("WinJS.UI.ListView.ModeIsInvalid", _ErrorMessages.modeIsInvalid);
                    }
                },

                /// <field type="String" oamOptionsDatatype="_UI.TapBehavior" locid="WinJS.UI.ListView.tapBehavior" helpKeyword="WinJS.UI.ListView.tapBehavior">
                /// Gets or sets how the ListView reacts when the user taps or clicks an item.
                /// The tap or click can invoke the item, select it and invoke it, or have no
                /// effect.
                /// </field>
                tapBehavior: {
                    get: function () {
                        return this._tap;
                    },
                    set: function (tap) {
                        if (_BaseUtils.isPhone && tap === _UI.TapBehavior.directSelect) {
                            return;
                        }
                        this._tap = tap;
                        this._updateItemsAriaRoles();
                        this._configureSelectionMode();
                    }
                },

                /// <field type="String" oamOptionsDatatype="WinJS.UI.GroupHeaderTapBehavior" locid="WinJS.UI.ListView.groupHeaderTapBehavior" helpKeyword="WinJS.UI.ListView.groupHeaderTapBehavior">
                /// Gets or sets how the ListView reacts when the user taps or clicks a group header.
                /// </field>
                groupHeaderTapBehavior: {
                    get: function () {
                        return this._groupHeaderTap;
                    },
                    set: function (tap) {
                        this._groupHeaderTap = tap;
                        this._updateGroupHeadersAriaRoles();
                    }
                },

                /// <field type="String" oamOptionsDatatype="WinJS.UI.SwipeBehavior" locid="WinJS.UI.ListView.swipeBehavior" helpKeyword="WinJS.UI.ListView.swipeBehavior">
                /// Gets or sets how the ListView reacts to the swipe interaction.
                /// The swipe gesture can select the swiped items or it can
                /// have no effect on the current selection.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                swipeBehavior: {
                    get: function () {
                        return this._swipeBehavior;
                    },
                    set: function (swipeBehavior) {
                        this._swipeBehavior = swipeBehavior;
                        this._setSwipeClass();
                    }
                },

                /// <field type="Object" locid="WinJS.UI.ListView.itemDataSource" helpKeyword="WinJS.UI.ListView.itemDataSource">
                /// Gets or sets the data source that provides items for the ListView.
                /// </field>
                itemDataSource: {
                    get: function () {
                        return this._itemsManager.dataSource;
                    },
                    set: function (newData) {
                        this._writeProfilerMark("set_itemDataSource,info");
                        this._dataSource = newData || new BindingList.List().dataSource;
                        this._groupFocusCache.clear();

                        if (!this._runningInit) {
                            this._selection._reset();
                            this._cancelAsyncViewWork(true);
                            this._updateItemsManager();
                            this._pendingLayoutReset = true;
                            this._batchViewUpdates(_Constants._ViewChange.rebuild, _Constants._ScrollToPriority.medium, 0, true);
                        }
                    }
                },

                /// <field type="Object" locid="WinJS.UI.ListView.itemTemplate" helpKeyword="WinJS.UI.ListView.itemTemplate" potentialValueSelector="[data-win-control='WinJS.Binding.Template']">
                /// Gets or sets the templating function that creates the DOM elements
                /// for each item in the itemDataSource. Each item can contain multiple
                /// DOM elements, but we recommend that it have a single root element.
                /// </field>
                itemTemplate: {
                    get: function () {
                        return this._itemRenderer;
                    },
                    set: function (newRenderer) {
                        this._setRenderer(newRenderer, false);

                        if (!this._runningInit) {
                            this._cancelAsyncViewWork(true);
                            this._updateItemsManager();
                            this._pendingLayoutReset = true;
                            this._batchViewUpdates(_Constants._ViewChange.rebuild, _Constants._ScrollToPriority.medium, 0, true);
                        }
                    }
                },

                /// <field type="Function" locid="WinJS.UI.ListView.resetItem" helpKeyword="WinJS.UI.ListView.resetItem">
                /// Gets or sets the function that is called when the ListView recycles the
                /// element representation of an item.
                /// <deprecated type="deprecate">
                /// resetItem may be altered or unavailable in future versions. Instead, mark the element as disposable using WinJS.Utilities.markDisposable.
                /// </deprecated>
                /// </field>
                resetItem: {
                    get: function () {
                        return this._itemRelease;
                    },
                    set: function (release) {
                        _ElementUtilities._deprecated(_ErrorMessages.resetItemIsDeprecated);
                        this._itemRelease = release;
                    }
                },

                /// <field type="Object" locid="WinJS.UI.ListView.groupHeaderTemplate" helpKeyword="WinJS.UI.ListView.groupHeaderTemplate" potentialValueSelector="[data-win-control='WinJS.Binding.Template']">
                /// Gets or sets the templating function that creates the DOM elements
                /// for each group header in the groupDataSource. Each group header
                /// can contain multiple elements, but it must have a single root element.
                /// </field>
                groupHeaderTemplate: {
                    get: function () {
                        return this._groupHeaderRenderer;
                    },
                    set: function (newRenderer) {
                        this._setRenderer(newRenderer, true);

                        if (!this._runningInit) {
                            this._cancelAsyncViewWork(true);
                            this._pendingLayoutReset = true;
                            this._batchViewUpdates(_Constants._ViewChange.rebuild, _Constants._ScrollToPriority.medium, 0, true);
                        }
                    }
                },

                /// <field type="Function" locid="WinJS.UI.ListView.resetGroupHeader" helpKeyword="WinJS.UI.ListView.resetGroupHeader" isAdvanced="true">
                /// Gets or sets the function that is called when the ListView recycles the DOM element representation
                /// of a group header.
                /// <deprecated type="deprecate">
                /// resetGroupHeader may be altered or unavailable in future versions. Instead, mark the header element as disposable using WinJS.Utilities.markDisposable.
                /// </deprecated>
                /// </field>
                resetGroupHeader: {
                    get: function () {
                        return this._groupHeaderRelease;
                    },
                    set: function (release) {
                        _ElementUtilities._deprecated(_ErrorMessages.resetGroupHeaderIsDeprecated);
                        this._groupHeaderRelease = release;
                    }
                },

                /// <field type="String" hidden="true" locid="WinJS.UI.ListView.loadingState" helpKeyword="WinJS.UI.ListView.loadingState">
                /// Gets a value that indicates whether the ListView is still loading or whether
                /// loading is complete.  This property can return one of these values:
                /// "itemsLoading", "viewPortLoaded", "itemsLoaded", or "complete".
                /// </field>
                loadingState: {
                    get: function () {
                        return this._loadingState;
                    }
                },

                /// <field type="Object" locid="WinJS.UI.ListView.selection" helpKeyword="WinJS.UI.ListView.selection" isAdvanced="true">
                /// Gets an ISelection object that contains the currently selected items.
                /// </field>
                selection: {
                    get: function () {
                        return this._selection;
                    }
                },

                /// <field type="Number" integer="true" locid="WinJS.UI.ListView.indexOfFirstVisible" helpKeyword="WinJS.UI.ListView.indexOfFirstVisible" isAdvanced="true">
                /// Gets or sets the first visible item. When setting this property, the ListView scrolls so that the
                /// item with the specified index is at the top of the list.
                /// </field>
                indexOfFirstVisible: {
                    get: function () {
                        return this._view.firstIndexDisplayed;
                    },

                    set: function (itemIndex) {
                        if (itemIndex < 0) {
                            return;
                        }

                        this._writeProfilerMark("set_indexOfFirstVisible(" + itemIndex + "),info");
                        this._raiseViewLoading(true);

                        var that = this;
                        this._batchViewUpdates(_Constants._ViewChange.realize, _Constants._ScrollToPriority.high, function () {
                            var range;
                            return that._entityInRange({ type: _UI.ObjectType.item, index: itemIndex }).then(function (validated) {
                                if (!validated.inRange) {
                                    return {
                                        position: 0,
                                        direction: "left"
                                    };
                                } else {
                                    return that._getItemOffset({ type: _UI.ObjectType.item, index: validated.index }).then(function (r) {
                                        range = r;
                                        return that._ensureFirstColumnRange(_UI.ObjectType.item);
                                    }).then(function () {
                                        range = that._correctRangeInFirstColumn(range, _UI.ObjectType.item);
                                        range = that._convertFromCanvasCoordinates(range);

                                        return that._view.waitForValidScrollPosition(range.begin);
                                    }).then(function (begin) {
                                        var direction = (begin < that._lastScrollPosition) ? "left" : "right";
                                        var max = that._viewport[that._scrollLength] - that._getViewportLength();
                                        begin = _ElementUtilities._clamp(begin, 0, max);

                                        return {
                                            position: begin,
                                            direction: direction
                                        };
                                    });
                                }
                            });
                        }, true);
                    }
                },

                /// <field type="Number" integer="true" readonly="true" locid="WinJS.UI.ListView.indexOfLastVisible" helpKeyword="WinJS.UI.ListView.indexOfLastVisible" isAdvanced="true">
                /// Gets the index of the last visible item.
                /// </field>
                indexOfLastVisible: {
                    get: function () {
                        return this._view.lastIndexDisplayed;
                    }
                },

                /// <field type="Object" locid="WinJS.UI.ListView.currentItem" helpKeyword="WinJS.UI.ListView.currentItem" isAdvanced="true">
                /// Gets or sets an object that indicates which item should get keyboard focus and its focus status.
                /// The object has these properties:
                /// index: the index of the item in the itemDataSource.
                /// key: the key of the item in the itemDataSource.
                /// hasFocus: when getting this property, this value is true if the item already has focus; otherwise, it's false.
                /// When setting this property, set this value to true if the item should get focus immediately; otherwise, set it to
                /// false and the item will get focus eventually.
                /// showFocus: true if the item displays the focus rectangle; otherwise, false.
                /// </field>
                currentItem: {
                    get: function () {
                        var focused = this._selection._getFocused();
                        var retVal = {
                            index: focused.index,
                            type: focused.type,
                            key: null,
                            hasFocus: !!this._hasKeyboardFocus,
                            showFocus: false
                        };
                        if (focused.type === _UI.ObjectType.groupHeader) {
                            var group = this._groups.group(focused.index);
                            if (group) {
                                retVal.key = group.key;
                                retVal.showFocus = !!(group.header && _ElementUtilities.hasClass(group.header, _Constants._itemFocusClass));
                            }
                        } else {
                            var item = this._view.items.itemAt(focused.index);
                            if (item) {
                                var record = this._itemsManager._recordFromElement(item);
                                retVal.key = record.item && record.item.key;
                                retVal.showFocus = !!item.parentNode.querySelector("." + _Constants._itemFocusOutlineClass);
                            }
                        }
                        return retVal;
                    },

                    set: function (data) {
                        this._hasKeyboardFocus = data.hasFocus || this._hasKeyboardFocus;
                        var that = this;
                        function setItemFocused(item, isInTree, entity) {
                            var drawKeyboardFocus = !!data.showFocus && that._hasKeyboardFocus;
                            that._unsetFocusOnItem(isInTree);
                            that._selection._setFocused(entity, drawKeyboardFocus);
                            if (that._hasKeyboardFocus) {
                                that._keyboardFocusInbound = drawKeyboardFocus;
                                that._setFocusOnItem(entity);
                            } else {
                                that._tabManager.childFocus = (isInTree ? item : null);
                            }
                            if (entity.type !== _UI.ObjectType.groupHeader) {
                                that._updateFocusCache(entity.index);
                                if (that._updater) {
                                    that._updater.newSelectionPivot = entity.index;
                                    that._updater.oldSelectionPivot = -1;
                                }
                                that._selection._pivot = entity.index;
                            }
                        }

                        if (data.key &&
                            ((data.type !== _UI.ObjectType.groupHeader && this._dataSource.itemFromKey) ||
                            (data.type === _UI.ObjectType.groupHeader && this._groupDataSource && this._groupDataSource.itemFromKey))) {
                            if (this.oldCurrentItemKeyFetch) {
                                this.oldCurrentItemKeyFetch.cancel();
                            }
                            var dataSource = (data.type === _UI.ObjectType.groupHeader ? this._groupDataSource : this._dataSource);
                            this.oldCurrentItemKeyFetch = dataSource.itemFromKey(data.key).then(function (item) {
                                that.oldCurrentItemKeyFetch = null;
                                if (item) {
                                    var element = (data.type === _UI.ObjectType.groupHeader ? that._groups.group(item.index).header : that._view.items.itemAt(item.index));
                                    setItemFocused(element, !!element, { type: data.type || _UI.ObjectType.item, index: item.index });
                                }
                            });
                        } else {
                            if (data.index !== undefined) {
                                var element;
                                if (data.type === _UI.ObjectType.groupHeader) {
                                    var group = that._groups.group(data.index);
                                    element = group && group.header;
                                } else {
                                    element = that._view.items.itemAt(data.index);
                                }
                                setItemFocused(element, !!element, { type: data.type || _UI.ObjectType.item, index: data.index });
                            }
                        }
                    }
                },

                /// <field type="Object" locid="WinJS.UI.ListView.zoomableView" helpKeyword="WinJS.UI.ListView.zoomableView" isAdvanced="true">
                /// Gets a ZoomableView. This API supports the SemanticZoom infrastructure
                /// and is not intended to be used directly from your code.
                /// </field>
                zoomableView: {
                    get: function () {
                        if (!this._zoomableView) {
                            this._zoomableView = new ZoomableView(this);
                        }

                        return this._zoomableView;
                    }
                },

                /// <field type="Boolean" locid="WinJS.UI.ListView.itemsDraggable" helpKeyword="WinJS.UI.ListView.itemsDraggable">
                /// Gets or sets whether the ListView's items can be dragged via drag and drop.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                itemsDraggable: {
                    get: function () {
                        return this._dragSource;
                    },

                    set: function (value) {
                        if (_BaseUtils.isPhone) {
                            return;
                        }
                        if (this._dragSource !== value) {
                            this._dragSource = value;
                            this._setSwipeClass();
                        }
                    }
                },

                /// <field type="Boolean" locid="WinJS.UI.ListView.itemsReorderable" helpKeyword="WinJS.UI.ListView.itemsReorderable">
                /// Gets or sets whether the ListView's items can be reordered within itself via drag and drop. When a ListView is marked as reorderable, its items can be dragged about inside itself, but it will not require the itemdrag events it fires to be handled.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                itemsReorderable: {
                    get: function () {
                        return this._reorderable;
                    },

                    set: function (value) {
                        if (_BaseUtils.isPhone) {
                            return;
                        }
                        if (this._reorderable !== value) {
                            this._reorderable = value;
                            this._setSwipeClass();
                        }
                    }
                },

                /// <field type="Number" integer="true" locid="WinJS.UI.ListView.maxDeferredItemCleanup" helpKeyword="WinJS.UI.ListView.maxDeferredItemCleanup" isAdvanced="true">
                /// Gets or sets the maximum number of realized items.
                /// </field>
                maxDeferredItemCleanup: {
                    get: function () {
                        return this._maxDeferredItemCleanup;
                    },

                    set: function (value) {
                        this._maxDeferredItemCleanup = Math.max(0, +value || 0);
                    }
                },

                // Public methods

                dispose: function () {
                    /// <signature helpKeyword="WinJS.UI.ListView.dispose">
                    /// <summary locid="WinJS.UI.ListView.dispose">
                    /// Disposes this ListView.
                    /// </summary>
                    /// </signature>
                    this._dispose();
                },

                elementFromIndex: function (itemIndex) {
                    /// <signature helpKeyword="WinJS.UI.ListView.elementFromIndex">
                    /// <summary locid="WinJS.UI.ListView.elementFromIndex">
                    /// Returns the DOM element that represents the item at the specified index.
                    /// </summary>
                    /// <param name="itemIndex" type="Number" integer="true" locid="WinJS.UI.ListView.elementFromIndex_p:itemIndex">
                    /// The index of the item.
                    /// </param>
                    /// <returns type="Object" domElement="true" locid="WinJS.UI.ListView.elementFromIndex_returnValue">
                    /// The DOM element that represents the specified item.
                    /// </returns>
                    /// </signature>

                    return this._view.items.itemAt(itemIndex);
                },

                indexOfElement: function (element) {
                    /// <signature helpKeyword="WinJS.UI.ListView.indexOfElement">
                    /// <summary locid="WinJS.UI.ListView.indexOfElement">
                    /// Returns the index of the item that the specified DOM element displays.
                    /// </summary>
                    /// <param name="element" type="HTMLElement" domElement="true" locid="WinJS.UI.ListView.indexOfElement_p:element">
                    /// The DOM element that displays the item.
                    /// </param>
                    /// <returns type="Number" integer="true" locid="WinJS.UI.ListView.indexOfElement_returnValue">
                    /// The index of item that the specified DOM element displays.
                    /// </returns>
                    /// </signature>

                    return this._view.items.index(element);
                },

                ensureVisible: function ListView_ensureVisible(value) {
                    /// <signature helpKeyword="WinJS.UI.ListView.ensureVisible">
                    /// <summary locid="WinJS.UI.ListView.ensureVisible">
                    /// Makes the specified item visible. The ListView scrolls to the item if needed.
                    /// </summary>
                    /// <param name="value" type="Number|IListViewEntity" integer="true" locid="WinJS.UI.ListView.ensureVisible_p:value">
                    /// The index of the ListView item or IListViewEntity to bring into view.
                    /// </param>
                    /// </signature>
                    var type = _UI.ObjectType.item,
                        itemIndex = value;
                    if (+value !== value) {
                        type = value.type;
                        itemIndex = value.index;
                    }
                    this._writeProfilerMark("ensureVisible(" + type + ": " + itemIndex + "),info");

                    if (itemIndex < 0) {
                        return;
                    }

                    this._raiseViewLoading(true);

                    var that = this;
                    this._batchViewUpdates(_Constants._ViewChange.realize, _Constants._ScrollToPriority.high, function () {
                        var range;

                        return that._entityInRange({ type: type, index: itemIndex }).then(function (validated) {
                            if (!validated.inRange) {
                                return {
                                    position: 0,
                                    direction: "left"
                                };
                            } else {
                                return that._getItemOffset({ type: type, index: validated.index }).then(function (r) {
                                    range = r;
                                    return that._ensureFirstColumnRange(type);
                                }).then(function () {
                                    range = that._correctRangeInFirstColumn(range, type);

                                    var viewportLength = that._getViewportLength(),
                                        left = that._viewportScrollPosition,
                                        right = left + viewportLength,
                                        newPosition = that._viewportScrollPosition,
                                        entityWidth = range.end - range.begin;

                                    range = that._convertFromCanvasCoordinates(range);

                                    var handled = false;
                                    if (type === _UI.ObjectType.groupHeader && left <= range.begin) {
                                        // EnsureVisible on a group where the entire header is fully visible does not
                                        // scroll. This prevents tabbing from an item in a very large group to align
                                        // the scroll to the header element.
                                        var header = that._groups.group(validated.index).header;
                                        if (header) {
                                            var headerEnd;
                                            var margins = _Layouts._getMargins(header);
                                            if (that._horizontalLayout) {
                                                var rtl = that._rtl();
                                                var headerStart = (rtl ? getOffsetRight(header) - margins.right : header.offsetLeft - margins.left);
                                                headerEnd = headerStart + header.offsetWidth + (rtl ? margins.left : margins.right);
                                            } else {
                                                headerEnd = header.offsetTop + header.offsetHeight + margins.top;
                                            }
                                            handled = headerEnd <= right;
                                        }
                                    }
                                    if (!handled) {
                                        if (entityWidth >= right - left) {
                                            // This item is larger than the viewport so we will just set
                                            // the scroll position to the beginning of the item.
                                            newPosition = range.begin;
                                        } else {
                                            if (range.begin < left) {
                                                newPosition = range.begin;
                                            } else if (range.end > right) {
                                                newPosition = range.end - viewportLength;
                                            }
                                        }
                                    }

                                    var direction = (newPosition < that._lastScrollPosition) ? "left" : "right";
                                    var max = that._viewport[that._scrollLength] - viewportLength;
                                    newPosition = _ElementUtilities._clamp(newPosition, 0, max);

                                    return {
                                        position: newPosition,
                                        direction: direction
                                    };
                                });
                            }
                        });
                    }, true);
                },

                loadMorePages: function ListView_loadMorePages() {
                    /// <signature helpKeyword="WinJS.UI.ListView.loadMorePages">
                    /// <summary locid="WinJS.UI.ListView.loadMorePages">
                    /// Loads the next set of pages if the ListView object's loadingBehavior is set to incremental.
                    /// <deprecated type="deprecate">
                    /// loadMorePages is deprecated. Invoking this function will not have any effect. Please refer to the 'ListView loading behaviors' SDK Sample for guidance on how to implement incremental load behavior.
                    /// </deprecated>
                    /// </summary>
                    /// </signature>
                    _ElementUtilities._deprecated(_ErrorMessages.loadMorePagesIsDeprecated);
                },

                recalculateItemPosition: function ListView_recalculateItemPosition() {
                    /// <signature helpKeyword="WinJS.UI.ListView.recalculateItemPosition">
                    /// <summary locid="WinJS.UI.ListView.recalculateItemPosition">
                    /// Repositions all the visible items in the ListView to adjust for items whose sizes have changed. Use this function or forceLayout when making the ListView visible again after you set its style.display property to "none" or after style changes have been made that affect the size or position of the ListView or its items. Unlike forceLayout, this method doesn’t recreate items and it doesn’t display entrance animation.
                    /// </summary>
                    /// </signature>
                    this._writeProfilerMark("recalculateItemPosition,info");
                    this._forceLayoutImpl(_Constants._ViewChange.relayout);
                },

                forceLayout: function ListView_forceLayout() {
                    /// <signature helpKeyword="WinJS.UI.ListView.forceLayout">
                    /// <summary locid="WinJS.UI.ListView.forceLayout">
                    /// Forces the ListView to update its layout. Use this function or relcaculateItemPosition when making the ListView visible again after you set its style.display property to "none” or after style changes have been made that affect the size or position of the ListView or its items.
                    /// after you set its style.display property to "none".
                    /// </summary>
                    /// </signature>
                    this._writeProfilerMark("forceLayout,info");
                    this._forceLayoutImpl(_Constants._ViewChange.remeasure);
                },

                _entityInRange: function ListView_entityInRange(entity) {
                    if (entity.type === _UI.ObjectType.item) {
                        return this._itemsCount().then(function (itemsCount) {
                            var index = _ElementUtilities._clamp(entity.index, 0, itemsCount - 1);
                            return {
                                inRange: index >= 0 && index < itemsCount,
                                index: index
                            };
                        });
                    } else {
                        var index = _ElementUtilities._clamp(entity.index, 0, this._groups.length() - 1);
                        return Promise.wrap({
                            inRange: index >= 0 && index < this._groups.length(),
                            index: index
                        });
                    }
                },

                _forceLayoutImpl: function ListView_forceLayoutImpl(viewChange) {
                    var that = this;
                    this._versionManager.unlocked.then(function () {
                        that._writeProfilerMark("_forceLayoutImpl viewChange(" + viewChange + "),info");

                        that._cancelAsyncViewWork();
                        that._pendingLayoutReset = true;
                        that._resizeViewport();

                        that._batchViewUpdates(viewChange, _Constants._ScrollToPriority.low, function () {
                            return {
                                position: that._lastScrollPosition,
                                direction: "right"
                            };
                        }, true, true);
                    });
                },

                _configureSelectionMode: function () {
                    if (_BaseUtils.isPhone) {
                        if (this.tapBehavior === _UI.TapBehavior.toggleSelect && this.selectionMode === _UI.SelectionMode.multi) {
                            _ElementUtilities.addClass(this._canvas, _Constants._selectionModeClass);
                        } else {
                            _ElementUtilities.removeClass(this._canvas, _Constants._selectionModeClass);
                        }
                    }
                },

                _lastScrollPosition: {
                    get: function () {
                        return this._lastScrollPositionValue;
                    },
                    set: function (position) {
                        if (position === 0) {
                            this._lastDirection = "right";
                            this._direction  = "right";
                            this._lastScrollPositionValue = 0;
                        } else {
                            var currentDirection = position < this._lastScrollPositionValue ? "left" : "right";
                            this._direction = this._scrollDirection(position);
                            this._lastDirection = currentDirection;
                            this._lastScrollPositionValue = position;
                        }
                    }
                },

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

                _viewportScrollPosition: {
                    get: function () {
                        this._currentScrollPosition = _ElementUtilities.getScrollPosition(this._viewport)[this._scrollProperty];
                        return this._currentScrollPosition;
                    },
                    set: function (value) {
                        var newScrollPos = {};
                        newScrollPos[this._scrollProperty] = value;
                        _ElementUtilities.setScrollPosition(this._viewport, newScrollPos);
                        this._currentScrollPosition = value;
                    }
                },

                _canvasStart: {
                    get: function () {
                        return this._canvasStartValue || 0;
                    },
                    set: function (value) {
                        var transformX = this._horizontal() ? (this._rtl() ? -value : value) : 0,
                            transformY = this._horizontal() ? 0 : value;
                        if (value !== 0) {
                            this._canvas.style[transformNames.scriptName] = "translate( " + transformX + "px, " + transformY + "px)";
                        } else {
                            this._canvas.style[transformNames.scriptName] = "";
                        }
                        this._canvasStartValue = value;
                    }
                },

                /// <field type="Number" integer="true" locid="WinJS.UI.ListView.scrollPosition" helpKeyword="WinJS.UI.ListView.scrollPosition">
                /// Gets or sets the position of the ListView's scrollbar.
                /// </field>
                scrollPosition: {
                    get: function () {
                        return this._viewportScrollPosition;
                    },
                    set: function (newPosition) {
                        var that = this;
                        this._batchViewUpdates(_Constants._ViewChange.realize, _Constants._ScrollToPriority.high, function () {
                            return that._view.waitForValidScrollPosition(newPosition).then(function () {
                                var max = that._viewport[that._scrollLength] - that._getViewportLength();
                                newPosition = _ElementUtilities._clamp(newPosition, 0, max);
                                var direction = (newPosition < that._lastScrollPosition) ? "left" : "right";
                                return {
                                    position: newPosition,
                                    direction: direction
                                };
                            });
                        }, true);
                    }
                },

                _setRenderer: function ListView_setRenderer(newRenderer, isGroupHeaderRenderer) {
                    var renderer;
                    if (!newRenderer) {
                        if (_BaseUtils.validation) {
                            throw new _ErrorFromName("WinJS.UI.ListView.invalidTemplate", _ErrorMessages.invalidTemplate);
                        }
                        renderer = _ItemsManager.trivialHtmlRenderer;
                    } else if (typeof newRenderer === "function") {
                        renderer = newRenderer;
                    } else if (typeof newRenderer === "object") {
                        if (_BaseUtils.validation && !newRenderer.renderItem) {
                            throw new _ErrorFromName("WinJS.UI.ListView.invalidTemplate", _ErrorMessages.invalidTemplate);
                        }
                        renderer = newRenderer.renderItem;
                    }

                    if (renderer) {
                        if (isGroupHeaderRenderer) {
                            this._groupHeaderRenderer = renderer;
                        } else {
                            this._itemRenderer = renderer;
                        }
                    }
                },

                _renderWithoutReuse: function ListView_renderWithoutReuse(itemPromise, oldElement) {
                    if (oldElement) {
                        _Dispose._disposeElement(oldElement);
                    }
                    var templateResult = this._itemRenderer(itemPromise);
                    if (templateResult.then) {
                        return templateResult.then(function (element) {
                            element.tabIndex = 0;
                            return element;
                        });
                    } else {
                        var element = templateResult.element || templateResult;
                        element.tabIndex = 0;
                        return templateResult;
                    }
                },

                _isInsertedItem: function ListView_isInsertedItem(itemPromise) {
                    return !!this._insertedItems[itemPromise.handle];
                },

                _clearInsertedItems: function ListView_clearInsertedItems() {
                    var keys = Object.keys(this._insertedItems);
                    for (var i = 0, len = keys.length; i < len; i++) {
                        this._insertedItems[keys[i]].release();
                    }
                    this._insertedItems = {};
                    this._modifiedElements = [];
                    this._countDifference = 0;
                },

                // Private methods
                _cancelAsyncViewWork: function (stopTreeCreation) {
                    this._view.stopWork(stopTreeCreation);
                },

                _updateView: function ListView_updateView() {
                    if (this._isZombie()) { return; }

                    var that = this;
                    function resetCache() {
                        that._itemsBlockExtent = -1;
                        that._firstItemRange = null;
                        that._firstHeaderRange = null;
                        that._itemMargins = null;
                        that._headerMargins = null;
                        that._canvasMargins = null;
                        that._cachedRTL = null;
                        // Retrieve the values before DOM modifications occur
                        that._rtl();
                    }

                    var viewChange = this._viewChange;
                    this._viewChange = _Constants._ViewChange.realize;

                    function functorWrapper() {
                        that._scrollToPriority = _Constants._ScrollToPriority.uninitialized;
                        var setScrollbarPosition = that._setScrollbarPosition;
                        that._setScrollbarPosition = false;

                        var position = typeof that._scrollToFunctor === "number" ? { position: that._scrollToFunctor } : that._scrollToFunctor();
                        return Promise.as(position).then(
                            function (scroll) {
                                scroll = scroll || {};
                                if (setScrollbarPosition && +scroll.position === scroll.position) {
                                    that._lastScrollPosition = scroll.position;
                                    that._viewportScrollPosition = scroll.position;
                                }
                                return scroll;
                            },
                            function (error) {
                                that._setScrollbarPosition |= setScrollbarPosition;
                                return Promise.wrapError(error);
                            }
                        );
                    }

                    if (viewChange === _Constants._ViewChange.rebuild) {
                        if (this._pendingGroupWork) {
                            this._updateGroupWork();
                        }
                        if (this._pendingLayoutReset) {
                            this._resetLayout();
                        }
                        resetCache();
                        if (!this._firstTimeDisplayed) {
                            this._view.reset();
                        }
                        this._view.reload(functorWrapper, true);
                        this._setFocusOnItem(this._selection._getFocused());
                    } else if (viewChange === _Constants._ViewChange.remeasure) {
                        this._view.resetItems(true);
                        this._resetLayout();
                        resetCache();
                        this._view.refresh(functorWrapper);
                        this._setFocusOnItem(this._selection._getFocused());
                    } else if (viewChange === _Constants._ViewChange.relayout) {
                        if (this._pendingLayoutReset) {
                            this._resetLayout();
                            resetCache();
                        }
                        this._view.refresh(functorWrapper);
                    } else {
                        this._view.onScroll(functorWrapper);
                    }
                },

                _batchViewUpdates: function ListView_batchViewUpdates(viewChange, scrollToPriority, positionFunctor, setScrollbarPosition, skipFadeout) {
                    this._viewChange = Math.min(this._viewChange, viewChange);

                    if (this._scrollToFunctor === null || scrollToPriority >= this._scrollToPriority) {
                        this._scrollToPriority = scrollToPriority;
                        this._scrollToFunctor = positionFunctor;
                    }

                    this._setScrollbarPosition |= !!setScrollbarPosition;

                    if (!this._batchingViewUpdates) {
                        this._raiseViewLoading();

                        var that = this;
                        this._batchingViewUpdatesSignal = new _Signal();
                        this._batchingViewUpdates = Promise.any([this._batchingViewUpdatesSignal.promise, Scheduler.schedulePromiseHigh(null, "WinJS.UI.ListView._updateView")]).then(function () {
                            if (that._isZombie()) { return; }

                            // If we're displaying for the first time, or there were no items visible in the view, we can skip the fade out animation
                            // and go straight to the refresh. _view.items._itemData.length is the most trustworthy way to find how many items are visible.
                            if (that._viewChange === _Constants._ViewChange.rebuild && !that._firstTimeDisplayed && Object.keys(that._view.items._itemData).length !== 0 && !skipFadeout) {
                                return that._fadeOutViewport();
                            }
                        }).then(
                            function () {
                                that._batchingViewUpdates = null;
                                that._batchingViewUpdatesSignal = null;
                                that._updateView();
                                that._firstTimeDisplayed = false;
                            },
                            function () {
                                that._batchingViewUpdates = null;
                                that._batchingViewUpdatesSignal = null;
                            }
                        );
                    }

                    return this._batchingViewUpdatesSignal;
                },

                _resetCanvas: function () {
                    if (this._disposed) {
                        return;
                    }

                    // Layouts do not currently support saving the scroll position when forceLayout() is called.
                    // Layouts need to recreate the canvas because the tabManager is there and you don't want to
                    // construct 2 instances of WinJS.UI.TabContainer for the same element.
                    var newCanvas = _Global.document.createElement('div');
                    newCanvas.className = this._canvas.className;
                    this._viewport.replaceChild(newCanvas, this._canvas);
                    this._canvas = newCanvas;
                    this._groupsToRemove = {};
                    // We reset the itemCanvas on _resetCanvas in case a ListView client uses two separate custom layouts, and each layout
                    // changes different styles on the itemCanvas without resetting it.
                    this._canvas.appendChild(this._canvasProxy);
                },

                _setupInternalTree: function ListView_setupInternalTree() {
                    _ElementUtilities.addClass(this._element, _Constants._listViewClass);
                    _ElementUtilities[this._rtl() ? "addClass" : "removeClass"](this._element, _Constants._rtlListViewClass);

                    this._element.innerHTML =
                        '<div tabIndex="-1" role="group" class="' + _Constants._viewportClass + ' ' + _Constants._horizontalClass + '">' +
                            '<div class="' + _Constants._scrollableClass + '">' +
                                // Create a proxy element inside the canvas so that during an MSPointerDown event we can call
                                // msSetPointerCapture on it. This allows hover to not be passed to it which saves a large invalidation.
                                '<div class="' + _Constants._proxyClass + '"></div>' +
                            '</div>' +
                            '<div></div>' +
                        '</div>' +
                        // The keyboard event helper is a dummy node that allows us to keep getting keyboard events when a virtualized element
                        // gets discarded. It has to be positioned in the center of the viewport, though, otherwise calling .focus() on it
                        // can move our viewport around when we don't want it moved.
                        // The keyboard event helper element will be skipped in the tab order if it doesn't have width+height set on it.
                       '<div aria-hidden="true" style="position:absolute;left:50%;top:50%;width:0px;height:0px;" tabindex="-1"></div>';

                    this._viewport = this._element.firstElementChild;
                    this._canvas = this._viewport.firstElementChild;
                    this._canvasProxy = this._canvas.firstElementChild;
                    // The deleteWrapper div is used to maintain the scroll width (after delete(s)) until the animation is done
                    this._deleteWrapper = this._canvas.nextElementSibling;
                    this._keyboardEventsHelper = this._viewport.nextElementSibling;
                    this._tabIndex = _ElementUtilities.getTabIndex(this._element);
                    if (this._tabIndex < 0) {
                        this._tabIndex = 0;
                    }
                    this._tabManager = new _TabContainer.TabContainer(this._viewport);
                    this._tabManager.tabIndex = this._tabIndex;

                    this._progressBar = _Global.document.createElement("progress");
                    _ElementUtilities.addClass(this._progressBar, _Constants._progressClass);
                    this._progressBar.style.position = "absolute";
                    this._progressBar.max = 100;
                },

                _unsetFocusOnItem: function ListView_unsetFocusOnItem(newFocusExists) {
                    if (this._tabManager.childFocus) {
                        this._clearFocusRectangle(this._tabManager.childFocus);
                    }
                    if (this._isZombie()) {
                        return;
                    }
                    if (!newFocusExists) {
                        // _setFocusOnItem may run asynchronously so prepare the keyboardEventsHelper
                        // to receive focus.
                        if (this._tabManager.childFocus) {
                            this._tabManager.childFocus = null;
                        }

                        this._keyboardEventsHelper._shouldHaveFocus = false;
                        // If the viewport has focus, leave it there. This will prevent focus from jumping
                        // from the viewport to the keyboardEventsHelper when scrolling with Narrator Touch.
                        if (_Global.document.activeElement !== this._viewport && this._hasKeyboardFocus) {
                            this._keyboardEventsHelper._shouldHaveFocus = true;
                            _ElementUtilities._setActive(this._keyboardEventsHelper);
                        }
                    }
                    this._itemFocused = false;
                },

                _setFocusOnItem: function ListView_setFocusOnItem(entity) {
                    this._writeProfilerMark("_setFocusOnItem,info");
                    if (this._focusRequest) {
                        this._focusRequest.cancel();
                    }
                    if (this._isZombie()) {
                        return;
                    }
                    var that = this;
                    var setFocusOnItemImpl = function (item) {
                        if (that._isZombie()) {
                            return;
                        }

                        if (that._tabManager.childFocus !== item) {
                            that._tabManager.childFocus = item;
                        }
                        that._focusRequest = null;
                        if (that._hasKeyboardFocus && !that._itemFocused) {
                            if (that._selection._keyboardFocused()) {
                                that._drawFocusRectangle(item);
                            }
                            // The requestItem promise just completed so _cachedCount will
                            // be initialized.
                            that._view.updateAriaForAnnouncement(item, (entity.type === _UI.ObjectType.groupHeader ? that._groups.length() : that._cachedCount));

                            // Some consumers of ListView listen for item invoked events and hide the listview when an item is clicked.
                            // Since keyboard interactions rely on async operations, sometimes an invoke event can be received before we get
                            // to WinJS.Utilities._setActive(item), and the listview will be made invisible. If that happens and we call item.setActive(), an exception
                            // is raised for trying to focus on an invisible item. Checking visibility is non-trivial, so it's best
                            // just to catch the exception and ignore it.
                            that._itemFocused = true;
                            _ElementUtilities._setActive(item);
                        }
                    };

                    if (entity.type !== _UI.ObjectType.groupHeader) {
                        this._focusRequest = this._view.items.requestItem(entity.index);
                    } else {
                        this._focusRequest = this._groups.requestHeader(entity.index);
                    }
                    this._focusRequest.then(setFocusOnItemImpl);
                },

                _attachEvents: function ListView_attachEvents() {
                    var that = this;

                    function listViewHandler(eventName, caseSensitive, capture) {
                        return {
                            name: (caseSensitive ? eventName : eventName.toLowerCase()),
                            handler: function (eventObject) {
                                that["_on" + eventName](eventObject);
                            },
                            capture: capture
                        };
                    }

                    function modeHandler(eventName, caseSensitive, capture) {
                        return {
                            capture: capture,
                            name: (caseSensitive ? eventName : eventName.toLowerCase()),
                            handler: function (eventObject) {
                                var currentMode = that._mode,
                                    name = "on" + eventName;
                                if (!that._disposed && currentMode[name]) {
                                    currentMode[name](eventObject);
                                }
                            }
                        };
                    }

                    function observerHandler(handlerName, attributesFilter) {
                        return {
                            handler: function (listOfChanges) {
                                that["_on" + handlerName](listOfChanges);
                            },
                            filter: attributesFilter
                        };
                    }

                    // Observers for specific element attribute changes
                    var elementObservers = [
                        observerHandler("PropertyChange", ["dir", "style", "tabindex"])
                    ];
                    this._cachedStyleDir = this._element.style.direction;

                    elementObservers.forEach(function (elementObserver) {
                        new _ElementUtilities._MutationObserver(elementObserver.handler).observe(that._element, { attributes: true, attributeFilter: elementObserver.filter });
                    });

                    // KeyDown handler needs to be added explicitly via addEventListener instead of using attachEvent.
                    // If it's not added via addEventListener, the eventObject given to us on event does not have the functions stopPropagation() and preventDefault().
                    var events = [
                        modeHandler("PointerDown"),
                        modeHandler("click", false),
                        modeHandler("PointerUp"),
                        modeHandler("LostPointerCapture"),
                        modeHandler("MSHoldVisual", true),
                        modeHandler("PointerCancel", true),
                        modeHandler("DragStart"),
                        modeHandler("DragOver"),
                        modeHandler("DragEnter"),
                        modeHandler("DragLeave"),
                        modeHandler("Drop"),
                        modeHandler("ContextMenu"),
                        modeHandler("MSManipulationStateChanged", true, true)
                    ];
                    events.forEach(function (eventHandler) {
                        _ElementUtilities._addEventListener(that._viewport, eventHandler.name, eventHandler.handler, !!eventHandler.capture);
                    });

                    var elementEvents = [
                        listViewHandler("FocusIn", false, false),
                        listViewHandler("FocusOut", false, false),
                        modeHandler("KeyDown"),
                        modeHandler("KeyUp"),
                        listViewHandler("MSElementResize", false, false)
                    ];
                    elementEvents.forEach(function (eventHandler) {
                        _ElementUtilities._addEventListener(that._element, eventHandler.name, eventHandler.handler, !!eventHandler.capture);
                    });
                    this._onMSElementResizeBound = this._onMSElementResize.bind(this);
                    _ElementUtilities._resizeNotifier.subscribe(this._element, this._onMSElementResizeBound);

                    var viewportEvents = [
                        listViewHandler("MSManipulationStateChanged", true),
                        listViewHandler("Scroll")
                    ];
                    viewportEvents.forEach(function (viewportEvent) {
                        that._viewport.addEventListener(viewportEvent.name, viewportEvent.handler, false);
                    });
                    this._viewport.addEventListener("onTabEnter", this._onTabEnter.bind(this));
                    this._viewport.addEventListener("onTabExit", this._onTabExit.bind(this));
                    this._viewport.addEventListener("onTabEntered", function (e) {
                        that._mode.onTabEntered(e);
                    });
                    this._viewport.addEventListener("onTabExiting", function (e) {
                        that._mode.onTabExiting(e);
                    });
                },

                _updateItemsManager: function ListView_updateItemsManager() {
                    var that = this,
                        notificationHandler = {
                            // Following methods are used by ItemsManager
                            beginNotifications: function ListView_beginNotifications() {
                            },

                            changed: function ListView_changed(newItem, oldItem) {
                                if (that._ifZombieDispose()) { return; }

                                that._createUpdater();

                                var elementInfo = that._updater.elements[uniqueID(oldItem)];
                                if (elementInfo) {
                                    var selected = that.selection._isIncluded(elementInfo.index);
                                    if (selected) {
                                        that._updater.updateDrag = true;
                                    }

                                    if (oldItem !== newItem) {
                                        if (that._tabManager.childFocus === oldItem || that._updater.newFocusedItem === oldItem) {
                                            that._updater.newFocusedItem = newItem;
                                            that._tabManager.childFocus = null;
                                        }

                                        if (elementInfo.itemBox) {
                                            _ElementUtilities.addClass(newItem, _Constants._itemClass);
                                            that._setupAriaSelectionObserver(newItem);

                                            var next = oldItem.nextElementSibling;
                                            elementInfo.itemBox.removeChild(oldItem);
                                            elementInfo.itemBox.insertBefore(newItem, next);
                                        }

                                        that._setAriaSelected(newItem, selected);
                                        that._view.items.setItemAt(elementInfo.newIndex, {
                                            element: newItem,
                                            itemBox: elementInfo.itemBox,
                                            container: elementInfo.container,
                                            itemsManagerRecord: elementInfo.itemsManagerRecord
                                        });
                                        delete that._updater.elements[uniqueID(oldItem)];
                                        _Dispose._disposeElement(oldItem);
                                        that._updater.elements[uniqueID(newItem)] = {
                                            item: newItem,
                                            container: elementInfo.container,
                                            itemBox: elementInfo.itemBox,
                                            index: elementInfo.index,
                                            newIndex: elementInfo.newIndex,
                                            itemsManagerRecord: elementInfo.itemsManagerRecord
                                        };
                                    } else if (elementInfo.itemBox && elementInfo.container) {
                                        _ItemEventsHandler._ItemEventsHandler.renderSelection(elementInfo.itemBox, newItem, selected, true);
                                        _ElementUtilities[selected ? "addClass" : "removeClass"](elementInfo.container, _Constants._selectedClass);
                                    }
                                    that._updater.changed = true;
                                }
                                for (var i = 0, len = that._notificationHandlers.length; i < len; i++) {
                                    that._notificationHandlers[i].changed(newItem, oldItem);
                                }
                                that._writeProfilerMark("changed,info");
                            },

                            removed: function ListView_removed(item, mirage, handle) {
                                if (that._ifZombieDispose()) { return; }

                                that._createUpdater();

                                function removeFromSelection(index) {
                                    that._updater.updateDrag = true;
                                    if (that._currentMode()._dragging && that._currentMode()._draggingUnselectedItem && that._currentMode()._dragInfo._isIncluded(index)) {
                                        that._updater.newDragInfo = new _SelectionManager._Selection(that, []);
                                    }

                                    var firstRange = that._updater.selectionFirst[index],
                                        lastRange = that._updater.selectionLast[index],
                                        range = firstRange || lastRange;

                                    if (range) {
                                        delete that._updater.selectionFirst[range.oldFirstIndex];
                                        delete that._updater.selectionLast[range.oldLastIndex];
                                        that._updater.selectionChanged = true;
                                    }
                                }

                                var insertedItem = that._insertedItems[handle];
                                if (insertedItem) {
                                    delete that._insertedItems[handle];
                                }

                                var index;
                                if (item) {
                                    var elementInfo = that._updater.elements[uniqueID(item)],
                                        itemObject = that._itemsManager.itemObject(item);

                                    if (itemObject) {
                                        that._groupFocusCache.deleteItem(itemObject.key);
                                    }

                                    if (elementInfo) {
                                        index = elementInfo.index;

                                        // We track removed elements for animation purposes (layout
                                        // component consumes this).
                                        //
                                        if (elementInfo.itemBox) {
                                            that._updater.removed.push({
                                                index: index,
                                                itemBox: elementInfo.itemBox
                                            });
                                        }
                                        that._updater.deletesCount++;

                                        // The view can't access the data from the itemsManager
                                        // anymore, so we need to flag the itemData that it
                                        // has been removed.
                                        //
                                        var itemData = that._view.items.itemDataAt(index);
                                        itemData.removed = true;

                                        delete that._updater.elements[uniqueID(item)];
                                    } else {
                                        index = itemObject && itemObject.index;
                                    }

                                    if (that._updater.oldFocus.type !== _UI.ObjectType.groupHeader && that._updater.oldFocus.index === index) {
                                        that._updater.newFocus.index = index; // If index is too high, it'll be fixed in endNotifications
                                        that._updater.focusedItemRemoved = true;
                                    }

                                    removeFromSelection(index);
                                } else {
                                    index = that._updater.selectionHandles[handle];
                                    if (index === +index) {
                                        removeFromSelection(index);
                                    }
                                }
                                that._writeProfilerMark("removed(" + index + "),info");

                                that._updater.changed = true;
                            },

                            updateAffectedRange: function ListView_updateAffectedRange(newerRange) {
                                that._itemsCount().then(function (count) {
                                    // When we receive insertion notifications before all of the containers have
                                    // been created and the affected range is beyond the container range, the
                                    // affected range indices will not correspond to the indices of the containers
                                    // created by updateContainers. In this case, start the affected range at the end
                                    // of the containers so that the affected range includes any containers that get
                                    // appended due to this batch of notifications.
                                    var containerCount = that._view.containers ? that._view.containers.length : 0;
                                    newerRange.start = Math.min(newerRange.start, containerCount);

                                    that._affectedRange.add(newerRange, count);
                                });
                                that._createUpdater();
                                that._updater.changed = true;
                            },

                            indexChanged: function ListView_indexChanged(item, newIndex, oldIndex) {
                                // We should receive at most one indexChanged notification per oldIndex
                                // per notification cycle.
                                if (that._ifZombieDispose()) { return; }

                                that._createUpdater();

                                if (item) {
                                    var itemObject = that._itemsManager.itemObject(item);
                                    if (itemObject) {
                                        that._groupFocusCache.updateItemIndex(itemObject.key, newIndex);
                                    }

                                    var elementInfo = that._updater.elements[uniqueID(item)];
                                    if (elementInfo) {
                                        elementInfo.newIndex = newIndex;
                                        that._updater.changed = true;
                                    }
                                    that._updater.itemsMoved = true;
                                }
                                if (that._currentMode()._dragging && that._currentMode()._draggingUnselectedItem && that._currentMode()._dragInfo._isIncluded(oldIndex)) {
                                    that._updater.newDragInfo = new _SelectionManager._Selection(that, [{ firstIndex: newIndex, lastIndex: newIndex }]);
                                    that._updater.updateDrag = true;
                                }

                                if (that._updater.oldFocus.type !== _UI.ObjectType.groupHeader && that._updater.oldFocus.index === oldIndex) {
                                    that._updater.newFocus.index = newIndex;
                                    that._updater.changed = true;
                                }

                                if (that._updater.oldSelectionPivot === oldIndex) {
                                    that._updater.newSelectionPivot = newIndex;
                                    that._updater.changed = true;
                                }

                                var range = that._updater.selectionFirst[oldIndex];
                                if (range) {
                                    range.newFirstIndex = newIndex;
                                    that._updater.changed = true;
                                    that._updater.updateDrag = true;
                                }
                                range = that._updater.selectionLast[oldIndex];
                                if (range) {
                                    range.newLastIndex = newIndex;
                                    that._updater.changed = true;
                                    that._updater.updateDrag = true;
                                }
                            },

                            endNotifications: function ListView_endNotifications() {
                                that._update();
                            },

                            inserted: function ListView_inserted(itemPromise) {
                                if (that._ifZombieDispose()) { return; }
                                that._writeProfilerMark("inserted,info");

                                that._createUpdater();
                                that._updater.changed = true;
                                itemPromise.retain();
                                that._updater.insertsCount++;
                                that._insertedItems[itemPromise.handle] = itemPromise;
                            },

                            moved: function ListView_moved(item, previous, next, itemPromise) {
                                if (that._ifZombieDispose()) { return; }

                                that._createUpdater();

                                that._updater.movesCount++;
                                if (item) {
                                    that._updater.itemsMoved = true;

                                    var elementInfo = that._updater.elements[uniqueID(item)];
                                    if (elementInfo) {
                                        elementInfo.moved = true;
                                    }
                                }

                                var index = that._updater.selectionHandles[itemPromise.handle];
                                if (index === +index) {
                                    that._updater.updateDrag = true;

                                    var firstRange = that._updater.selectionFirst[index],
                                        lastRange = that._updater.selectionLast[index],
                                        range = firstRange || lastRange;

                                    if (range && range.oldFirstIndex !== range.oldLastIndex) {
                                        delete that._updater.selectionFirst[range.oldFirstIndex];
                                        delete that._updater.selectionLast[range.oldLastIndex];
                                        that._updater.selectionChanged = true;
                                        that._updater.changed = true;
                                    }
                                }
                                that._writeProfilerMark("moved(" + index + "),info");
                            },

                            countChanged: function ListView_countChanged(newCount, oldCount) {
                                if (that._ifZombieDispose()) { return; }
                                that._writeProfilerMark("countChanged(" + newCount + "),info");

                                that._cachedCount = newCount;
                                that._createUpdater();

                                if ((that._view.lastIndexDisplayed + 1) === oldCount) {
                                    that._updater.changed = true;
                                }

                                that._updater.countDifference += newCount - oldCount;
                            },

                            reload: function ListView_reload() {
                                if (that._ifZombieDispose()) {
                                    return;
                                }
                                that._writeProfilerMark("reload,info");

                                that._processReload();
                            }
                        };

                    function statusChanged(eventObject) {
                        if (eventObject.detail === _UI.DataSourceStatus.failure) {
                            that.itemDataSource = null;
                            that.groupDataSource = null;
                        }
                    }

                    if (this._versionManager) {
                        this._versionManager._dispose();
                    }

                    this._versionManager = new _VersionManager._VersionManager();
                    this._updater = null;

                    var ranges = this._selection.getRanges();
                    this._selection._selected.clear();

                    if (this._itemsManager) {

                        if (this._itemsManager.dataSource && this._itemsManager.dataSource.removeEventListener) {
                            this._itemsManager.dataSource.removeEventListener("statuschanged", statusChanged, false);
                        }

                        this._clearInsertedItems();
                        this._itemsManager.release();
                    }

                    if (this._itemsCountPromise) {
                        this._itemsCountPromise.cancel();
                        this._itemsCountPromise = null;
                    }
                    this._cachedCount = _Constants._UNINITIALIZED;

                    this._itemsManager = _ItemsManager._createItemsManager(
                        this._dataSource,
                        this._renderWithoutReuse.bind(this),
                        notificationHandler,
                        {
                            ownerElement: this._element,
                            versionManager: this._versionManager,
                            indexInView: function (index) {
                                return (index >= that.indexOfFirstVisible && index <= that.indexOfLastVisible);
                            },
                            viewCallsReady: true,
                            profilerId: this._id
                        });

                    if (this._dataSource.addEventListener) {
                        this._dataSource.addEventListener("statuschanged", statusChanged, false);
                    }

                    this._selection._selected.set(ranges);
                },

                _processReload: function () {
                    this._affectedRange.addAll();

                    // Inform scroll view that a realization pass is coming so that it doesn't restart the
                    // realization pass itself.
                    this._cancelAsyncViewWork(true);
                    if (this._currentMode()._dragging) {
                        this._currentMode()._clearDragProperties();
                    }

                    this._groupFocusCache.clear();
                    this._selection._reset();
                    this._updateItemsManager();
                    this._pendingLayoutReset = true;
                    this._batchViewUpdates(_Constants._ViewChange.rebuild, _Constants._ScrollToPriority.low, this.scrollPosition);
                },

                _createUpdater: function ListView_createUpdater() {
                    if (!this._updater) {
                        if (this.itemDataSource._isVirtualizedDataSource) {
                            // VDS doesn't support the _updateAffectedRange notification so assume
                            // that everything needs to be relaid out.
                            this._affectedRange.addAll();
                        }
                        this._versionManager.beginUpdating();

                        // Inform scroll view that a realization pass is coming so that it doesn't restart the
                        // realization pass itself.
                        this._cancelAsyncViewWork();

                        var updater = {
                            changed: false,
                            elements: {},
                            selectionFirst: {},
                            selectionLast: {},
                            selectionHandles: {},
                            oldSelectionPivot: { type: _UI.ObjectType.item, index: _Constants._INVALID_INDEX },
                            newSelectionPivot: { type: _UI.ObjectType.item, index: _Constants._INVALID_INDEX },
                            removed: [],
                            selectionChanged: false,
                            oldFocus: { type: _UI.ObjectType.item, index: _Constants._INVALID_INDEX },
                            newFocus: { type: _UI.ObjectType.item, index: _Constants._INVALID_INDEX },
                            hadKeyboardFocus: this._hasKeyboardFocus,
                            itemsMoved: false,
                            lastVisible: this.indexOfLastVisible,
                            updateDrag: false,
                            movesCount: 0,
                            insertsCount: 0,
                            deletesCount: 0,
                            countDifference: 0
                        };

                        this._view.items.each(function (index, item, itemData) {
                            updater.elements[uniqueID(item)] = {
                                item: item,
                                container: itemData.container,
                                itemBox: itemData.itemBox,
                                index: index,
                                newIndex: index,
                                itemsManagerRecord: itemData.itemsManagerRecord,
                                detached: itemData.detached
                            };
                        });

                        var selection = this._selection._selected._ranges;
                        for (var i = 0, len = selection.length; i < len; i++) {
                            var range = selection[i];
                            var newRange = {
                                newFirstIndex: selection[i].firstIndex,
                                oldFirstIndex: selection[i].firstIndex,
                                newLastIndex: selection[i].lastIndex,
                                oldLastIndex: selection[i].lastIndex
                            };
                            updater.selectionFirst[newRange.oldFirstIndex] = newRange;
                            updater.selectionLast[newRange.oldLastIndex] = newRange;
                            updater.selectionHandles[range.firstPromise.handle] = newRange.oldFirstIndex;
                            updater.selectionHandles[range.lastPromise.handle] = newRange.oldLastIndex;
                        }
                        updater.oldSelectionPivot = this._selection._pivot;
                        updater.newSelectionPivot = updater.oldSelectionPivot;
                        updater.oldFocus = this._selection._getFocused();
                        updater.newFocus = this._selection._getFocused();

                        this._updater = updater;
                    }
                },

                _synchronize: function ListView_synchronize() {
                    var updater = this._updater;
                    this._updater = null;
                    this._groupsChanged = false;

                    this._countDifference = this._countDifference || 0;

                    if (updater && updater.changed) {
                        if (updater.itemsMoved) {
                            this._layout.itemsMoved && this._layout.itemsMoved();
                        }
                        if (updater.removed.length) {
                            this._layout.itemsRemoved && this._layout.itemsRemoved(updater.removed.map(function (node) {
                                return node.itemBox;
                            }));
                        }

                        if (updater.itemsMoved || updater.removed.length || Object.keys(this._insertedItems).length) {
                            this._layout.setupAnimations && this._layout.setupAnimations();
                        }

                        if (this._currentMode().onDataChanged) {
                            this._currentMode().onDataChanged();
                        }

                        var newSelection = [];
                        for (var i in updater.selectionFirst) {
                            if (updater.selectionFirst.hasOwnProperty(i)) {
                                var range = updater.selectionFirst[i];
                                updater.selectionChanged = updater.selectionChanged || ((range.newLastIndex - range.newFirstIndex) !== (range.oldLastIndex - range.oldFirstIndex));
                                if (range.newFirstIndex <= range.newLastIndex) {
                                    newSelection.push({
                                        firstIndex: range.newFirstIndex,
                                        lastIndex: range.newLastIndex
                                    });
                                }
                            }
                        }

                        if (updater.selectionChanged) {
                            var newSelectionItems = new _SelectionManager._Selection(this, newSelection);

                            // We do not allow listeners to cancel the selection
                            // change because the cancellation would also have to
                            // prevent the deletion.
                            this._selection._fireSelectionChanging(newSelectionItems);
                            this._selection._selected.set(newSelection);
                            this._selection._fireSelectionChanged();
                            newSelectionItems.clear();
                        } else {
                            this._selection._selected.set(newSelection);
                        }
                        this._selection._updateCount(this._cachedCount);
                        updater.newSelectionPivot = Math.min(this._cachedCount - 1, updater.newSelectionPivot);
                        this._selection._pivot = (updater.newSelectionPivot >= 0 ? updater.newSelectionPivot : _Constants._INVALID_INDEX);

                        if (updater.newFocus.type !== _UI.ObjectType.groupHeader) {
                            updater.newFocus.index = Math.max(0, Math.min(this._cachedCount - 1, updater.newFocus.index));
                        }
                        this._selection._setFocused(updater.newFocus, this._selection._keyboardFocused());

                        // If there are 2 edits before layoutAnimations runs we need to merge the 2 groups of modified elements.
                        // For example:
                        // If you start with A, B, C and add item Z to the beginning you will have
                        // [ -1 -> 0, 0 -> 1, 1 -> 2, 2 -> 3]
                        // However before layout is called an insert of Y to the beginning also happens you should get
                        // [ -1 -> 0, -1 -> 1, 0 -> 2, 1 -> 3, 2 -> 4]
                        var previousModifiedElements = this._modifiedElements || [];
                        var previousModifiedElementsHash = {};
                        this._modifiedElements = [];
                        this._countDifference += updater.countDifference;

                        for (i = 0; i < previousModifiedElements.length; i++) {
                            var modifiedElement = previousModifiedElements[i];
                            if (modifiedElement.newIndex === -1) {
                                this._modifiedElements.push(modifiedElement);
                            } else {
                                previousModifiedElementsHash[modifiedElement.newIndex] = modifiedElement;
                            }
                        }

                        for (i = 0; i < updater.removed.length; i++) {
                            var removed = updater.removed[i];
                            var modifiedElement = previousModifiedElementsHash[removed.index];
                            if (modifiedElement) {
                                delete previousModifiedElementsHash[removed.index];
                            } else {
                                modifiedElement = {
                                    oldIndex: removed.index
                                };
                            }
                            modifiedElement.newIndex = -1;
                            if (!modifiedElement._removalHandled) {
                                modifiedElement._itemBox = removed.itemBox;
                            }
                            this._modifiedElements.push(modifiedElement);
                        }

                        var insertedKeys = Object.keys(this._insertedItems);
                        for (i = 0; i < insertedKeys.length; i++) {
                            this._modifiedElements.push({
                                oldIndex: -1,
                                newIndex: this._insertedItems[insertedKeys[i]].index
                            });
                        }

                        this._writeProfilerMark("_synchronize:update_modifiedElements,StartTM");
                        var newItems = {};
                        for (i in updater.elements) {
                            if (updater.elements.hasOwnProperty(i)) {
                                var elementInfo = updater.elements[i];
                                newItems[elementInfo.newIndex] = {
                                    element: elementInfo.item,
                                    container: elementInfo.container,
                                    itemBox: elementInfo.itemBox,
                                    itemsManagerRecord: elementInfo.itemsManagerRecord,
                                    detached: elementInfo.detached
                                };

                                var modifiedElement = previousModifiedElementsHash[elementInfo.index];
                                if (modifiedElement) {
                                    delete previousModifiedElementsHash[elementInfo.index];
                                    modifiedElement.newIndex = elementInfo.newIndex;
                                } else {
                                    modifiedElement = {
                                        oldIndex: elementInfo.index,
                                        newIndex: elementInfo.newIndex
                                    };
                                }
                                modifiedElement.moved = elementInfo.moved;
                                this._modifiedElements.push(modifiedElement);
                            }
                        }
                        this._writeProfilerMark("_synchronize:update_modifiedElements,StopTM");

                        var previousIndices = Object.keys(previousModifiedElementsHash);
                        for (i = 0; i < previousIndices.length; i++) {
                            var key = previousIndices[i];
                            var modifiedElement = previousModifiedElementsHash[key];
                            if (modifiedElement.oldIndex !== -1) {
                                this._modifiedElements.push(modifiedElement);
                            }
                        }

                        this._view.items._itemData = newItems;
                        if (updater.updateDrag && this._currentMode()._dragging) {
                            if (!this._currentMode()._draggingUnselectedItem) {
                                this._currentMode()._dragInfo = this._selection;
                            } else if (updater.newDragInfo) {
                                this._currentMode()._dragInfo = updater.newDragInfo;
                            }
                            this._currentMode().fireDragUpdateEvent();
                        }

                        // If the focused item is removed, or the item we're trying to focus on has been moved before we can focus on it,
                        // we need to update our focus request to get the item from the appropriate index.
                        if (updater.focusedItemRemoved || (this._focusRequest && (updater.oldFocus.index !== updater.newFocus.index) || (updater.oldFocus.type !== updater.newFocus.type))) {
                            this._itemFocused = false;
                            this._setFocusOnItem(this._selection._getFocused());
                        } else if (updater.newFocusedItem) {
                            // We need to restore the value of _hasKeyboardFocus because a changed item
                            // gets removed from the DOM at the time of the notification. If the item
                            // had focus at that time, then our value of _hasKeyboardFocus will have changed.
                            this._hasKeyboardFocus = updater.hadKeyboardFocus;
                            this._itemFocused = false;
                            this._setFocusOnItem(this._selection._getFocused());
                        }

                        var that = this;
                        return this._groups.synchronizeGroups().then(function () {
                            if (updater.newFocus.type === _UI.ObjectType.groupHeader) {
                                updater.newFocus.index = Math.min(that._groups.length() - 1, updater.newFocus.index);

                                if (updater.newFocus.index < 0) {
                                    // An empty listview has currentFocus = item 0
                                    updater.newFocus = { type: _UI.ObjectType.item, index: 0 };
                                }
                                that._selection._setFocused(updater.newFocus, that._selection._keyboardFocused());
                            }

                            that._versionManager.endUpdating();
                            if (updater.deletesCount > 0) {
                                that._updateDeleteWrapperSize();
                            }

                            return that._view.updateTree(that._cachedCount, that._countDifference, that._modifiedElements);
                        }).then(function () {
                            return that._lastScrollPosition;
                        });
                    } else {
                        this._countDifference += updater ? updater.countDifference : 0;

                        var that = this;
                        return this._groups.synchronizeGroups().then(function ListView_synchronizeGroups_success_groupsChanged() {
                            updater && that._versionManager.endUpdating();
                            return that._view.updateTree(that._cachedCount, that._countDifference, that._modifiedElements);
                        }).then(function () {
                            return that.scrollPosition;
                        });
                    }
                },

                _updateDeleteWrapperSize: function ListView_updateDeleteWrapperSize(clear) {
                    var sizeProperty = this._horizontal() ? "width" : "height";
                    this._deleteWrapper.style["min-" + sizeProperty] = (clear ? 0 : this.scrollPosition + this._getViewportSize()[sizeProperty]) + "px";
                },

                _verifyRealizationNeededForChange: function ListView_skipRealization() {
                    // If the updater indicates that only deletes occurred, and we have not lost a viewport full of items,
                    // we skip realizing all the items and appending new ones until other action causes a full realize (e.g. scrolling).
                    //
                    var skipRealization = false;
                    var totalInViewport = (this._view.lastIndexDisplayed || 0) - (this._view.firstIndexDisplayed || 0);
                    var deletesOnly = this._updater && this._updater.movesCount === 0 && this._updater.insertsCount === 0 && this._updater.deletesCount > 0 && (this._updater.deletesCount === Math.abs(this._updater.countDifference));
                    if (deletesOnly && this._updater.elements) {
                        // Verify that the indices of the elements in the updater are within the valid range
                        var elementsKeys = Object.keys(this._updater.elements);
                        for (var i = 0, len = elementsKeys.length; i < len; i++) {
                            var element = this._updater.elements[elementsKeys[i]];
                            var delta = element.index - element.newIndex;
                            if (delta < 0 || delta > this._updater.deletesCount) {
                                deletesOnly = false;
                                break;
                            }
                        }
                    }
                    this._view.deletesWithoutRealize = this._view.deletesWithoutRealize || 0;

                    if (deletesOnly &&
                        (this._view.lastIndexDisplayed < this._view.end - totalInViewport) &&
                        (this._updater.deletesCount + this._view.deletesWithoutRealize) < totalInViewport) {

                        skipRealization = true;
                        this._view.deletesWithoutRealize += Math.abs(this._updater.countDifference);
                        this._writeProfilerMark("skipping realization on delete,info");
                    } else {
                        this._view.deletesWithoutRealize = 0;
                    }
                    this._view._setSkipRealizationForChange(skipRealization);
                },

                _update: function ListView_update() {
                    this._writeProfilerMark("update,StartTM");
                    if (this._ifZombieDispose()) { return; }

                    this._updateJob = null;

                    var that = this;
                    if (this._versionManager.noOutstandingNotifications) {
                        if (this._updater || this._groupsChanged) {
                            this._cancelAsyncViewWork();
                            this._verifyRealizationNeededForChange();
                            this._synchronize().then(function (scrollbarPos) {
                                that._writeProfilerMark("update,StopTM");
                                that._batchViewUpdates(_Constants._ViewChange.relayout, _Constants._ScrollToPriority.low, scrollbarPos).complete();
                            });
                        } else {
                            // Even if nothing important changed we need to restart aria work if it was canceled.
                            this._batchViewUpdates(_Constants._ViewChange.relayout, _Constants._ScrollToPriority.low, this._lastScrollPosition).complete();
                        }
                    }
                },

                _scheduleUpdate: function ListView_scheduleUpdate() {
                    if (!this._updateJob) {
                        var that = this;
                        // Batch calls to _scheduleUpdate
                        this._updateJob = Scheduler.schedulePromiseHigh(null, "WinJS.UI.ListView._update").then(function () {
                            if (that._updateJob) {
                                that._update();
                            }
                        });

                        this._raiseViewLoading();
                    }
                },

                _createGroupsContainer: function () {
                    if (this._groups) {
                        this._groups.cleanUp();
                    }

                    if (this._groupDataSource) {
                        this._groups = new _GroupsContainer._UnvirtualizedGroupsContainer(this, this._groupDataSource);
                    } else {
                        this._groups = new _GroupsContainer._NoGroups(this);
                    }
                },

                _createLayoutSite: function () {
                    var that = this;
                    return Object.create({
                        invalidateLayout: function () {
                            that._pendingLayoutReset = true;
                            var orientationChanged = (that._layout.orientation === "horizontal") !== that._horizontalLayout;
                            that._affectedRange.addAll();
                            that._batchViewUpdates(_Constants._ViewChange.rebuild, _Constants._ScrollToPriority.low, orientationChanged ? 0 : that.scrollPosition, false, true);
                        },
                        itemFromIndex: function (itemIndex) {
                            return that._itemsManager._itemPromiseAtIndex(itemIndex);
                        },
                        groupFromIndex: function (groupIndex) {
                            if (that._groupsEnabled()) {
                                return groupIndex < that._groups.length() ? that._groups.group(groupIndex).userData : null;
                            } else {
                                return { key: "-1" };
                            }
                        },
                        groupIndexFromItemIndex: function (itemIndex) {
                            // If itemIndex < 0, returns 0. If itemIndex is larger than the
                            // biggest item index, returns the last group index.
                            itemIndex = Math.max(0, itemIndex);
                            return that._groups.groupFromItem(itemIndex);
                        },
                        renderItem: function (itemPromise) {
                            return Promise._cancelBlocker(that._itemsManager._itemFromItemPromise(itemPromise)).then(function (element) {
                                if (element) {
                                    var record = that._itemsManager._recordFromElement(element);
                                    if (record.pendingReady) {
                                        record.pendingReady();
                                    }

                                    element = element.cloneNode(true);

                                    _ElementUtilities.addClass(element, _Constants._itemClass);

                                    var itemBox = _Global.document.createElement("div");
                                    _ElementUtilities.addClass(itemBox, _Constants._itemBoxClass);
                                    itemBox.appendChild(element);

                                    var container = _Global.document.createElement("div");
                                    _ElementUtilities.addClass(container, _Constants._containerClass);
                                    container.appendChild(itemBox);

                                    return container;
                                } else {
                                    return Promise.cancel;
                                }
                            });
                        },
                        renderHeader: function (group) {
                            var rendered = _ItemsManager._normalizeRendererReturn(that.groupHeaderTemplate(Promise.wrap(group)));
                            return rendered.then(function (headerRecord) {
                                _ElementUtilities.addClass(headerRecord.element, _Constants._headerClass);
                                var container = _Global.document.createElement("div");
                                _ElementUtilities.addClass(container, _Constants._headerContainerClass);
                                container.appendChild(headerRecord.element);
                                return container;
                            });
                        },
                        readyToMeasure: function () {
                            that._getViewportLength();
                            that._getCanvasMargins();
                        },
                        _isZombie: function () {
                            return that._isZombie();
                        },
                        _writeProfilerMark: function (text) {
                            that._writeProfilerMark(text);
                        }
                    }, {
                        _itemsManager: {
                            enumerable: true,
                            get: function () {
                                return that._itemsManager;
                            }
                        },
                        rtl: {
                            enumerable: true,
                            get: function () {
                                return that._rtl();
                            }
                        },
                        surface: {
                            enumerable: true,
                            get: function () {
                                return that._canvas;
                            }
                        },
                        viewport: {
                            enumerable: true,
                            get: function () {
                                return that._viewport;
                            }
                        },
                        scrollbarPos: {
                            enumerable: true,
                            get: function () {
                                return that.scrollPosition;
                            }
                        },
                        viewportSize: {
                            enumerable: true,
                            get: function () {
                                return that._getViewportSize();
                            }
                        },
                        loadingBehavior: {
                            enumerable: true,
                            get: function () {
                                return that.loadingBehavior;
                            }
                        },
                        animationsDisabled: {
                            enumerable: true,
                            get: function () {
                                return that._animationsDisabled();
                            }
                        },
                        tree: {
                            enumerable: true,
                            get: function () {
                                return that._view.tree;
                            }
                        },
                        realizedRange: {
                            enumerable: true,
                            get: function () {
                                return {
                                    firstPixel: Math.max(0, that.scrollPosition - 2 * that._getViewportLength()),
                                    lastPixel: that.scrollPosition + 3 * that._getViewportLength() - 1
                                };
                            }
                        },
                        visibleRange: {
                            enumerable: true,
                            get: function () {
                                return {
                                    firstPixel: that.scrollPosition,
                                    lastPixel: that.scrollPosition + that._getViewportLength() - 1
                                };
                            }
                        },
                        itemCount: {
                            enumerable: true,
                            get: function () {
                                return that._itemsCount();
                            }
                        },
                        groupCount: {
                            enumerable: true,
                            get: function () {
                                return that._groups.length();
                            }
                        }
                    });
                },

                _initializeLayout: function () {
                    this._affectedRange.addAll();
                    var layoutSite = this._createLayoutSite();
                    this._layout.initialize(layoutSite, this._groupsEnabled());
                    return this._layout.orientation === "horizontal";
                },

                _resetLayoutOrientation: function ListView_resetLayoutOrientation(resetScrollPosition) {
                    if (this._horizontalLayout) {
                        this._startProperty = "left";
                        this._scrollProperty = "scrollLeft";
                        this._scrollLength = "scrollWidth";
                        this._deleteWrapper.style.minHeight = "";
                        _ElementUtilities.addClass(this._viewport, _Constants._horizontalClass);
                        _ElementUtilities.removeClass(this._viewport, _Constants._verticalClass);
                        if (resetScrollPosition) {
                            this._viewport.scrollTop = 0;
                        }
                    } else {
                        this._startProperty = "top";
                        this._scrollProperty = "scrollTop";
                        this._scrollLength = "scrollHeight";
                        this._deleteWrapper.style.minWidth = "";
                        _ElementUtilities.addClass(this._viewport, _Constants._verticalClass);
                        _ElementUtilities.removeClass(this._viewport, _Constants._horizontalClass);
                        if (resetScrollPosition) {
                            _ElementUtilities.setScrollPosition(this._viewport, {scrollLeft: 0});
                        }
                    }
                },

                _resetLayout: function ListView_resetLayout() {
                    this._pendingLayoutReset = false;
                    this._affectedRange.addAll();
                    if (this._layout) {
                        this._layout.uninitialize();
                        this._horizontalLayout = this._initializeLayout();
                        this._resetLayoutOrientation();
                    }
                },

                _updateLayout: function ListView_updateLayout(layoutObject) {
                    var hadPreviousLayout = false;
                    if (this._layout) {
                        // The old layout is reset here in case it was in the middle of animating when the layout got changed. Reset
                        // will cancel out the animations.
                        this._cancelAsyncViewWork(true);
                        this._layout.uninitialize();
                        hadPreviousLayout = true;
                    }

                    var layoutImpl;
                    if (layoutObject && typeof layoutObject.type === "function") {
                        var LayoutCtor = requireSupportedForProcessing(layoutObject.type);
                        layoutImpl = new LayoutCtor(layoutObject);
                    } else if (layoutObject && (layoutObject.initialize)) {
                        layoutImpl = layoutObject;
                    } else {
                        layoutImpl = new _Layouts.GridLayout(layoutObject);
                    }

                    hadPreviousLayout && this._resetCanvas();

                    this._layoutImpl = layoutImpl;
                    this._layout = new _Layouts._LayoutWrapper(layoutImpl);

                    hadPreviousLayout && this._unsetFocusOnItem();
                    this._setFocusOnItem({ type: _UI.ObjectType.item, index: 0 });
                    this._selection._setFocused({ type: _UI.ObjectType.item, index: 0 });

                    this._horizontalLayout = this._initializeLayout();
                    this._resetLayoutOrientation(hadPreviousLayout);

                    if (hadPreviousLayout) {
                        this._canvas.style.width = this._canvas.style.height = "";
                    }
                },

                _currentMode: function ListView_currentMode() {
                    return this._mode;
                },

                _setSwipeClass: function ListView_setSwipeClass() {
                    // We apply an -ms-touch-action style to block panning and swiping from occurring at the same time. It is
                    // possible to pan in the margins between items and on lists without the swipe ability.
                    // Phone does not support swipe; therefore, we don't add them swipeable CSS class.
                    if (!_BaseUtils.isPhone && ((this._currentMode() instanceof _BrowseMode._SelectionMode && this._selectionAllowed() && this._swipeBehavior === _UI.SwipeBehavior.select) ||
                        this._dragSource || this._reorderable)) {
                        this._swipeable = true;
                        _ElementUtilities.addClass(this._element, _Constants._swipeableClass);
                    } else {
                        this._swipeable = false;
                        _ElementUtilities.removeClass(this._element, _Constants._swipeableClass);
                    }
                    var dragEnabled = (this.itemsDraggable || this.itemsReorderable),
                        swipeSelectEnabled = (this._selectionAllowed() && this._swipeBehavior === _UI.SwipeBehavior.select),
                        swipeEnabled = this._swipeable;

                    this._view.items.each(function (index, item, itemData) {
                        if (itemData.itemBox) {
                            var dragDisabledOnItem = _ElementUtilities.hasClass(item, _Constants._nonDraggableClass),
                                selectionDisabledOnItem = _ElementUtilities.hasClass(item, _Constants._nonSelectableClass),
                                nonSwipeable = _ElementUtilities.hasClass(itemData.itemBox, _Constants._nonSwipeableClass);
                            itemData.itemBox.draggable = (dragEnabled && !dragDisabledOnItem);
                            if (!swipeEnabled && nonSwipeable) {
                                _ElementUtilities.removeClass(itemData.itemBox, _Constants._nonSwipeableClass);
                            } else if (swipeEnabled) {
                                var makeNonSwipeable = (dragEnabled && !swipeSelectEnabled && dragDisabledOnItem) ||
                                                        (swipeSelectEnabled && !dragEnabled && selectionDisabledOnItem) ||
                                                        (dragDisabledOnItem && selectionDisabledOnItem);
                                if (makeNonSwipeable && !nonSwipeable) {
                                    _ElementUtilities.addClass(itemData.itemBox, _Constants._nonSwipeableClass);
                                } else if (!makeNonSwipeable && nonSwipeable) {
                                    _ElementUtilities.removeClass(itemData.itemBox, _Constants._nonSwipeableClass);
                                }
                            }
                            var makeNonSelectable = _BaseUtils.isPhone && selectionDisabledOnItem;
                            _ElementUtilities[makeNonSelectable ? "addClass" : "removeClass"](itemData.itemBox, _Constants._nonSelectableClass);
                        }
                    });
                },

                _resizeViewport: function ListView_resizeViewport() {
                    this._viewportWidth = _Constants._UNINITIALIZED;
                    this._viewportHeight = _Constants._UNINITIALIZED;
                },

                _onMSElementResize: function ListView_onResize() {
                    this._writeProfilerMark("_onMSElementResize,info");
                    Scheduler.schedule(function ListView_async_msElementResize() {
                        if (this._isZombie()) { return; }
                        // If these values are uninitialized there is already a realization pass pending.
                        if (this._viewportWidth !== _Constants._UNINITIALIZED && this._viewportHeight !== _Constants._UNINITIALIZED) {
                            var newWidth = this._element.offsetWidth,
                                newHeight = this._element.offsetHeight;
                            if ((this._previousWidth !== newWidth) || (this._previousHeight !== newHeight)) {

                                this._writeProfilerMark("resize (" + this._previousWidth + "x" + this._previousHeight + ") => (" + newWidth + "x" + newHeight + "),info");

                                this._previousWidth = newWidth;
                                this._previousHeight = newHeight;

                                this._resizeViewport();

                                var that = this;
                                this._affectedRange.addAll();
                                this._batchViewUpdates(_Constants._ViewChange.relayout, _Constants._ScrollToPriority.low, function () {
                                    return {
                                        position: that.scrollPosition,
                                        direction: "right"
                                    };
                                });
                            }
                        }
                    }, Scheduler.Priority.max, this, "WinJS.UI.ListView._onMSElementResize");
                },

                _onFocusIn: function ListView_onFocusIn(event) {
                    this._hasKeyboardFocus = true;
                    var that = this;
                    function moveFocusToItem(keyboardFocused) {
                        that._changeFocus(that._selection._getFocused(), true, false, false, keyboardFocused);
                    }
                    // The keyboardEventsHelper object can get focus through three ways: We give it focus explicitly, in which case _shouldHaveFocus will be true,
                    // or the item that should be focused isn't in the viewport, so keyboard focus could only go to our helper. The third way happens when
                    // focus was already on the keyboard helper and someone alt tabbed away from and eventually back to the app. In the second case, we want to navigate
                    // back to the focused item via changeFocus(). In the third case, we don't want to move focus to a real item. We differentiate between cases two and three
                    // by checking if the flag _keyboardFocusInbound is true. It'll be set to true when the tab manager notifies us about the user pressing tab
                    // to move focus into the listview.
                    if (event.target === this._keyboardEventsHelper) {
                        if (!this._keyboardEventsHelper._shouldHaveFocus && this._keyboardFocusInbound) {
                            moveFocusToItem(true);
                        } else {
                            this._keyboardEventsHelper._shouldHaveFocus = false;
                        }
                    } else if (event.target === this._element) {
                        // If someone explicitly calls .focus() on the listview element, we need to route focus to the item that should be focused
                        moveFocusToItem();
                    } else {
                        if (this._mode.inboundFocusHandled) {
                            this._mode.inboundFocusHandled = false;
                            return;
                        }

                        // In the event that .focus() is explicitly called on an element, we need to figure out what item got focus and set our state appropriately.
                        var items = this._view.items,
                            entity = {},
                            element = this._groups.headerFrom(event.target),
                            winItem = null;
                        if (element) {
                            entity.type = _UI.ObjectType.groupHeader;
                            entity.index = this._groups.index(element);
                        } else {
                            entity.index = items.index(event.target);
                            entity.type = _UI.ObjectType.item;
                            element = items.itemBoxAt(entity.index);
                            winItem = items.itemAt(entity.index);
                        }

                        // In the old layouts, index will be -1 if a group header got focus
                        if (entity.index !== _Constants._INVALID_INDEX) {
                            if (this._keyboardFocusInbound || this._selection._keyboardFocused()) {
                                if ((entity.type === _UI.ObjectType.groupHeader && event.target === element) ||
                                        (entity.type === _UI.ObjectType.item && event.target.parentNode === element)) {
                                    // For items we check the parentNode because the srcElement is win-item and element is win-itembox,
                                    // for header, they should both be the win-groupheader
                                    this._drawFocusRectangle(element);
                                }
                            }
                            if (this._tabManager.childFocus !== element && this._tabManager.childFocus !== winItem) {
                                this._selection._setFocused(entity, this._keyboardFocusInbound || this._selection._keyboardFocused());
                                this._keyboardFocusInbound = false;
                                element = entity.type === _UI.ObjectType.groupHeader ? element : items.itemAt(entity.index);
                                this._tabManager.childFocus = element;

                                if (that._updater) {
                                    var elementInfo = that._updater.elements[uniqueID(element)],
                                        focusIndex = entity.index;
                                    if (elementInfo && elementInfo.newIndex) {
                                        focusIndex = elementInfo.newIndex;
                                    }

                                    // Note to not set old and new focus to the same object
                                    that._updater.oldFocus = { type: entity.type, index: focusIndex };
                                    that._updater.newFocus = { type: entity.type, index: focusIndex };
                                }
                            }
                        }
                    }
                },

                _onFocusOut: function ListView_onFocusOut(event) {
                    if (this._disposed) {
                        return;
                    }

                    this._hasKeyboardFocus = false;
                    this._itemFocused = false;
                    var element = this._view.items.itemBoxFrom(event.target) || this._groups.headerFrom(event.target);
                    if (element) {
                        this._clearFocusRectangle(element);
                    }
                },

                _onMSManipulationStateChanged: function ListView_onMSManipulationStateChanged(ev) {
                    var that = this;
                    function done() {
                        that._manipulationEndSignal = null;
                    }

                    this._manipulationState = ev.currentState;
                    that._writeProfilerMark("_onMSManipulationStateChanged state(" + ev.currentState + "),info");

                    if (this._manipulationState !== _ElementUtilities._MSManipulationEvent.MS_MANIPULATION_STATE_STOPPED && !this._manipulationEndSignal) {
                        this._manipulationEndSignal = new _Signal();
                        this._manipulationEndSignal.promise.done(done, done);
                    }

                    if (this._manipulationState === _ElementUtilities._MSManipulationEvent.MS_MANIPULATION_STATE_STOPPED) {
                        this._manipulationEndSignal.complete();
                    }
                },

                _pendingScroll: false,

                _onScroll: function ListView_onScroll() {
                    if (!this._zooming && !this._pendingScroll) {
                        this._checkScroller();
                    }
                },

                _checkScroller: function ListView_checkScroller() {
                    if (this._isZombie()) { return; }

                    var currentScrollPosition = this._viewportScrollPosition;
                    if (currentScrollPosition !== this._lastScrollPosition) {
                        this._pendingScroll = _BaseUtils._requestAnimationFrame(this._checkScroller.bind(this));

                        currentScrollPosition = Math.max(0, currentScrollPosition);
                        var direction =  this._scrollDirection(currentScrollPosition);

                        this._lastScrollPosition = currentScrollPosition;
                        this._raiseViewLoading(true);
                        var that = this;
                        this._view.onScroll(function () {
                            return {
                                position: that._lastScrollPosition,
                                direction: direction
                            };
                        },
                            this._manipulationEndSignal ? this._manipulationEndSignal.promise : Promise.timeout(_Constants._DEFERRED_SCROLL_END));
                    } else {
                        this._pendingScroll = null;
                    }
                },

                _scrollDirection: function ListView_scrollDirectionl(currentScrollPosition) {
                    var currentDirection = currentScrollPosition < this._lastScrollPosition ? "left" : "right";

                    // When receiving a sequence of scroll positions, the browser may give us one scroll position
                    // which doesn't fit (e.g. the scroll positions were increasing but just this one is decreasing).
                    // To filter out this noise, _scrollDirection and _direction are stubborn -- they only change
                    // when we've received a sequence of 3 scroll position which all indicate the same direction.
                    return currentDirection === this._lastDirection ? currentDirection : this._direction;
                },

                _onTabEnter: function ListView_onTabEnter() {
                    this._keyboardFocusInbound = true;
                },

                _onTabExit: function ListView_onTabExit() {
                    this._keyboardFocusInbound = false;
                },

                _onPropertyChange: function ListView_onPropertyChange(list) {
                    var that = this;
                    list.forEach(function (record) {
                        var dirChanged = false;
                        if (record.attributeName === "dir") {
                            dirChanged = true;
                        } else if (record.attributeName === "style") {
                            dirChanged = (that._cachedStyleDir !== record.target.style.direction);
                        }
                        if (dirChanged) {
                            that._cachedStyleDir = record.target.style.direction;
                            that._cachedRTL = null;
                            _ElementUtilities[that._rtl() ? "addClass" : "removeClass"](that._element, _Constants._rtlListViewClass);

                            that._lastScrollPosition = 0;
                            that._viewportScrollPosition = 0;

                            that.forceLayout();
                        }

                        if (record.attributeName === "tabIndex") {
                            var newTabIndex = that._element.tabIndex;
                            if (newTabIndex >= 0) {
                                that._view.items.each(function (index, item) {
                                    item.tabIndex = newTabIndex;
                                });
                                that._tabIndex = newTabIndex;
                                that._tabManager.tabIndex = newTabIndex;
                                that._element.tabIndex = -1;
                            }
                        }
                    });
                },

                _getCanvasMargins: function ListView_getCanvasMargins() {
                    if (!this._canvasMargins) {
                        this._canvasMargins = _Layouts._getMargins(this._canvas);
                    }
                    return this._canvasMargins;
                },

                // Convert between canvas coordinates and viewport coordinates
                _convertCoordinatesByCanvasMargins: function ListView_convertCoordinatesByCanvasMargins(coordinates, conversionCallback) {
                    function fix(field, offset) {
                        if (coordinates[field] !== undefined) {
                            coordinates[field] = conversionCallback(coordinates[field], offset);
                        }
                    }

                    var offset;
                    if (this._horizontal()) {
                        offset = this._getCanvasMargins()[this._rtl() ? "right" : "left"];
                        fix("left", offset);
                    } else {
                        offset = this._getCanvasMargins().top;
                        fix("top", offset);
                    }
                    fix("begin", offset);
                    fix("end", offset);

                    return coordinates;
                },
                _convertFromCanvasCoordinates: function ListView_convertFromCanvasCoordinates(coordinates) {
                    return this._convertCoordinatesByCanvasMargins(coordinates, function (coordinate, canvasMargin) {
                        return coordinate + canvasMargin;
                    });
                },
                _convertToCanvasCoordinates: function ListView_convertToCanvasCoordinates(coordinates) {
                    return this._convertCoordinatesByCanvasMargins(coordinates, function (coordinate, canvasMargin) {
                        return coordinate - canvasMargin;
                    });
                },

                // Methods in the site interface used by ScrollView
                _getViewportSize: function ListView_getViewportSize() {
                    if (this._viewportWidth === _Constants._UNINITIALIZED || this._viewportHeight === _Constants._UNINITIALIZED) {
                        this._viewportWidth = Math.max(0, _ElementUtilities.getContentWidth(this._element));
                        this._viewportHeight = Math.max(0, _ElementUtilities.getContentHeight(this._element));
                        this._writeProfilerMark("viewportSizeDetected width:" + this._viewportWidth + " height:" + this._viewportHeight);

                        this._previousWidth = this._element.offsetWidth;
                        this._previousHeight = this._element.offsetHeight;
                    }
                    return {
                        width: this._viewportWidth,
                        height: this._viewportHeight
                    };
                },

                _itemsCount: function ListView_itemsCount() {
                    var that = this;
                    function cleanUp() {
                        that._itemsCountPromise = null;
                    }

                    if (this._cachedCount !== _Constants._UNINITIALIZED) {
                        return Promise.wrap(this._cachedCount);
                    } else {
                        var retVal;
                        if (!this._itemsCountPromise) {
                            retVal = this._itemsCountPromise = this._itemsManager.dataSource.getCount().then(
                                function (count) {
                                    if (count === _UI.CountResult.unknown) {
                                        count = 0;
                                    }
                                    that._cachedCount = count;
                                    that._selection._updateCount(that._cachedCount);
                                    return count;
                                },
                                function () {
                                    return Promise.cancel;
                                }
                            );

                            this._itemsCountPromise.then(cleanUp, cleanUp);
                        } else {
                            retVal = this._itemsCountPromise;
                        }

                        return retVal;
                    }
                },

                _isSelected: function ListView_isSelected(index) {
                    return this._selection._isIncluded(index);
                },

                _LoadingState: {
                    itemsLoading: "itemsLoading",
                    viewPortLoaded: "viewPortLoaded",
                    itemsLoaded: "itemsLoaded",
                    complete: "complete"
                },

                _raiseViewLoading: function ListView_raiseViewLoading(scrolling) {
                    if (this._loadingState !== this._LoadingState.itemsLoading) {
                        this._scrolling = !!scrolling;
                    }
                    this._setViewState(this._LoadingState.itemsLoading);
                },

                _raiseViewComplete: function ListView_raiseViewComplete() {
                    if (!this._disposed && !this._view.animating) {
                        this._setViewState(this._LoadingState.complete);
                    }
                },

                _setViewState: function ListView_setViewState(state) {
                    if (state !== this._loadingState) {
                        var detail = null;
                        // We can go from any state to itemsLoading but the rest of the states transitions must follow this
                        // order: itemsLoading -> viewPortLoaded -> itemsLoaded -> complete.
                        // Recursively set the previous state until you hit the current state or itemsLoading.
                        switch (state) {
                            case this._LoadingState.viewPortLoaded:
                                if (!this._scheduledForDispose) {
                                    scheduleForDispose(this);
                                    this._scheduledForDispose = true;
                                }
                                this._setViewState(this._LoadingState.itemsLoading);
                                break;

                            case this._LoadingState.itemsLoaded:
                                detail = {
                                    scrolling: this._scrolling
                                };
                                this._setViewState(this._LoadingState.viewPortLoaded);
                                break;

                            case this._LoadingState.complete:
                                this._setViewState(this._LoadingState.itemsLoaded);
                                this._updateDeleteWrapperSize(true);
                                break;
                        }

                        this._writeProfilerMark("loadingStateChanged:" + state + ",info");
                        this._loadingState = state;
                        var eventObject = _Global.document.createEvent("CustomEvent");
                        eventObject.initCustomEvent("loadingstatechanged", true, false, detail);
                        this._element.dispatchEvent(eventObject);
                    }
                },

                _createTemplates: function ListView_createTemplates() {

                    function createNodeWithClass(className, skipAriaHidden) {
                        var element = _Global.document.createElement("div");
                        element.className = className;
                        if (!skipAriaHidden) {
                            element.setAttribute("aria-hidden", true);
                        }
                        return element;
                    }

                    this._itemBoxTemplate = createNodeWithClass(_Constants._itemBoxClass, true);
                },

                // Methods used by SelectionManager
                _updateSelection: function ListView_updateSelection() {
                    var indices = this._selection.getIndices(),
                        selectAll = this._selection.isEverything(),
                        selectionMap = {};

                    if (!selectAll) {
                        for (var i = 0, len = indices.length ; i < len; i++) {
                            var index = indices[i];
                            selectionMap[index] = true;
                        }
                    }

                    this._view.items.each(function (index, element, itemData) {
                        if (itemData.itemBox && !_ElementUtilities.hasClass(itemData.itemBox, _Constants._swipeClass)) {
                            var selected = selectAll || !!selectionMap[index];
                            _ItemEventsHandler._ItemEventsHandler.renderSelection(itemData.itemBox, element, selected, true);
                            if (itemData.container) {
                                _ElementUtilities[selected ? "addClass" : "removeClass"](itemData.container, _Constants._selectedClass);
                            }
                        }
                    });
                },

                _getViewportLength: function ListView_getViewportLength() {
                    return this._getViewportSize()[this._horizontal() ? "width" : "height"];
                },

                _horizontal: function ListView_horizontal() {
                    return this._horizontalLayout;
                },

                _rtl: function ListView_rtl() {
                    if (typeof this._cachedRTL !== "boolean") {
                        this._cachedRTL = _Global.getComputedStyle(this._element, null).direction === "rtl";
                    }
                    return this._cachedRTL;
                },

                _showProgressBar: function ListView_showProgressBar(parent, x, y) {
                    var progressBar = this._progressBar,
                        progressStyle = progressBar.style;

                    if (!progressBar.parentNode) {
                        this._fadingProgressBar = false;
                        if (this._progressIndicatorDelayTimer) {
                            this._progressIndicatorDelayTimer.cancel();
                        }
                        var that = this;
                        this._progressIndicatorDelayTimer = Promise.timeout(_Constants._LISTVIEW_PROGRESS_DELAY).then(function () {
                            if (!that._isZombie()) {
                                parent.appendChild(progressBar);
                                AnimationHelper.fadeInElement(progressBar);
                                that._progressIndicatorDelayTimer = null;
                            }
                        });
                    }
                    progressStyle[this._rtl() ? "right" : "left"] = x;
                    progressStyle.top = y;
                },

                _hideProgressBar: function ListView_hideProgressBar() {
                    if (this._progressIndicatorDelayTimer) {
                        this._progressIndicatorDelayTimer.cancel();
                        this._progressIndicatorDelayTimer = null;
                    }

                    var progressBar = this._progressBar;
                    if (progressBar.parentNode && !this._fadingProgressBar) {
                        this._fadingProgressBar = true;
                        var that = this;
                        AnimationHelper.fadeOutElement(progressBar).then(function () {
                            if (progressBar.parentNode) {
                                progressBar.parentNode.removeChild(progressBar);
                            }
                            that._fadingProgressBar = false;
                        });
                    }
                },

                _getPanAxis: function () {
                    return this._horizontal() ? "horizontal" : "vertical";
                },

                _configureForZoom: function (isZoomedOut, isCurrentView, triggerZoom) {
                    if (_BaseUtils.validation) {
                        if (!this._view.realizePage || typeof this._view.begin !== "number") {
                            throw new _ErrorFromName("WinJS.UI.ListView.NotCompatibleWithSemanticZoom", strings.notCompatibleWithSemanticZoom);
                        }
                    }

                    this._isZoomedOut = isZoomedOut;
                    this._disableEntranceAnimation = !isCurrentView;

                    this._isCurrentZoomView = isCurrentView;

                    this._triggerZoom = triggerZoom;
                },

                _setCurrentItem: function (x, y) {
                    // First, convert the position into canvas coordinates
                    if (this._rtl()) {
                        x = this._viewportWidth - x;
                    }
                    if (this._horizontal()) {
                        x += this.scrollPosition;
                    } else {
                        y += this.scrollPosition;
                    }

                    var result = this._view.hitTest(x, y),
                        entity = { type: result.type ? result.type : _UI.ObjectType.item, index: result.index };
                    if (entity.index >= 0) {
                        if (this._hasKeyboardFocus) {
                            this._changeFocus(entity, true, false, true);
                        } else {
                            this._changeFocusPassively(entity);
                        }
                    }
                },

                _getCurrentItem: function () {
                    var focused = this._selection._getFocused();

                    if (focused.type === _UI.ObjectType.groupHeader) {
                        focused = { type: _UI.ObjectType.item, index: this._groups.group(focused.index).startIndex };
                    }

                    if (typeof focused.index !== "number") {
                        // Do a hit-test in the viewport center
                        this._setCurrentItem(0.5 * this._viewportWidth, 0.5 * this._viewportHeight);

                        focused = this._selection._getFocused();
                    }

                    var that = this;
                    var promisePosition = this._getItemOffsetPosition(focused.index).
                            then(function (posCanvas) {
                                var scrollOffset = that._canvasStart;

                                posCanvas[that._startProperty] += scrollOffset;

                                return posCanvas;
                            });

                    return Promise.join({
                        item: this._dataSource.itemFromIndex(focused.index),
                        position: promisePosition
                    });
                },

                _animateItemsForPhoneZoom: function () {
                    var containersOnScreen = [],
                        itemRows = [],
                        promises = [],
                        minRow = Number.MAX_VALUE,
                        that = this;

                    for (var i = this._view.firstIndexDisplayed, len = Math.min(this._cachedCount, this._view.lastIndexDisplayed + 1) ; i < len; i++) {
                        promises.push(this._view.waitForEntityPosition({ type: _UI.ObjectType.item, index: i }).then(function () {
                            containersOnScreen.push(that._view.items.containerAt(i));
                            var itemRow = 0;
                            if (that.layout._getItemPosition) {
                                var itemPosition = that.layout._getItemPosition(i);
                                if (itemPosition.row) {
                                    itemRow = itemPosition.row;
                                }
                            }
                            itemRows.push(itemRow);
                            minRow = Math.min(itemRow, minRow);
                        }));
                    }

                    function rowStaggerDelay(minRow, rows, delayBetweenRows) {
                        return function (index) {
                            return ((rows[index] - minRow) * delayBetweenRows);
                        };
                    }

                    function clearTransform() {
                        for (var i = 0, len = containersOnScreen.length; i < len; i++) {
                            containersOnScreen[i].style[transformNames.scriptName] = "";
                        }
                    }

                    return Promise.join(promises).then(function () {
                        return (containersOnScreen.length === 0 ? Promise.wrap() : _TransitionAnimation.executeTransition(
                            containersOnScreen,
                            {
                                property: transformNames.cssName,
                                delay: rowStaggerDelay(minRow, itemRows, 30),
                                duration: 100,
                                timing: "ease-in-out",
                                from: (!that._isCurrentZoomView ? "rotateX(-90deg)" : "rotateX(0deg)"),
                                to: (!that._isCurrentZoomView ? "rotateX(0deg)" : "rotateX(90deg)")
                            })).then(clearTransform, clearTransform);
                    }).then(clearTransform, clearTransform);
                },

                _beginZoom: function () {
                    this._zooming = true;
                    var zoomPromise = null;

                    if (_BaseUtils.isPhone) {
                        if (this._isZoomedOut) {
                            this._zoomAnimationPromise && this._zoomAnimationPromise.cancel();
                            // The phone's zoom animations need to be handled in two different spots.
                            // When zooming out, we need to wait for _positionItem to be called so that we have the right items in view before trying to animate.
                            // When zooming back in, the items we need to animate are already ready (and _positionItem won't be called on the zoomed out view, since it's
                            // being dismissed), so we play the animation in _beginZoom.
                            if (this._isCurrentZoomView) {
                                var that = this;
                                var animationComplete = function animationComplete() {
                                    that._zoomAnimationPromise = null;
                                };
                                this._zoomAnimationPromise = zoomPromise = this._animateItemsForPhoneZoom().then(animationComplete, animationComplete);
                            } else {
                                this._zoomAnimationPromise = new _Signal();
                                zoomPromise = this._zoomAnimationPromise.promise;
                            }
                        }
                    } else {
                        // Hide the scrollbar and extend the content beyond the ListView viewport
                        var horizontal = this._horizontal(),
                            scrollOffset = -this.scrollPosition;

                        _ElementUtilities.addClass(this._viewport, horizontal ? _Constants._zoomingXClass : _Constants._zoomingYClass);
                        this._canvasStart = scrollOffset;
                        _ElementUtilities.addClass(this._viewport, horizontal ? _Constants._zoomingYClass : _Constants._zoomingXClass);
                    }
                    return zoomPromise;
                },

                _positionItem: function (item, position) {
                    var that = this;
                    function positionItemAtIndex(index) {
                        return that._getItemOffsetPosition(index).then(function positionItemAtIndex_then_ItemOffsetPosition(posCanvas) {
                            var horizontal = that._horizontal(),
                                canvasSize = that._viewport[horizontal ? "scrollWidth" : "scrollHeight"],
                                viewportSize = (horizontal ? that._viewportWidth : that._viewportHeight),
                                headerSizeProp = (horizontal ? "headerContainerWidth" : "headerContainerHeight"),
                                layoutSizes = that.layout._sizes,
                                headerSize = 0,
                                scrollPosition;

                            if (layoutSizes && layoutSizes[headerSizeProp]) {
                                headerSize = layoutSizes[headerSizeProp];
                            }
                            // Align the leading edge
                            var start = (_BaseUtils.isPhone ? headerSize : position[that._startProperty]),
                                startMax = viewportSize - (horizontal ? posCanvas.width : posCanvas.height);

                            // Ensure the item ends up within the viewport
                            start = Math.max(0, Math.min(startMax, start));

                            scrollPosition = posCanvas[that._startProperty] - start;


                            // Ensure the scroll position is valid
                            var adjustedScrollPosition = Math.max(0, Math.min(canvasSize - viewportSize, scrollPosition)),
                            scrollAdjustment = adjustedScrollPosition - scrollPosition;

                            scrollPosition = adjustedScrollPosition;

                            var entity = { type: _UI.ObjectType.item, index: index };
                            if (that._hasKeyboardFocus) {
                                that._changeFocus(entity, true);
                            } else {
                                that._changeFocusPassively(entity);
                            }

                            that._raiseViewLoading(true);
                            // Since a zoom is in progress, adjust the div position
                            if (!_BaseUtils.isPhone) {
                                var scrollOffset = -scrollPosition;
                                that._canvasStart = scrollOffset;
                            } else {
                                that._viewportScrollPosition = scrollPosition;
                            }
                            that._view.realizePage(scrollPosition, true);

                            if (_BaseUtils.isPhone && that._isZoomedOut) {
                                var animationComplete = function animationComplete() {
                                    that._zoomAnimationPromise && that._zoomAnimationPromise.complete && that._zoomAnimationPromise.complete();
                                    that._zoomAnimationPromise = null;
                                };
                                that._animateItemsForPhoneZoom().then(animationComplete, animationComplete);
                            }
                            return (
                                horizontal ?
                            { x: scrollAdjustment, y: 0 } :
                            { x: 0, y: scrollAdjustment }
                            );
                        });
                    }

                    var itemIndex = 0;
                    if (item) {
                        itemIndex = (this._isZoomedOut ? item.groupIndexHint : item.firstItemIndexHint);
                    }

                    if (typeof itemIndex === "number") {
                        return positionItemAtIndex(itemIndex);
                    } else {
                        // We'll need to obtain the index from the data source
                        var itemPromise;

                        var key = (this._isZoomedOut ? item.groupKey : item.firstItemKey);
                        if (typeof key === "string" && this._dataSource.itemFromKey) {
                            itemPromise = this._dataSource.itemFromKey(key, (this._isZoomedOut ? {
                                groupMemberKey: item.key,
                                groupMemberIndex: item.index
                            } : null));
                        } else {
                            var description = (this._isZoomedOut ? item.groupDescription : item.firstItemDescription);

                            if (_BaseUtils.validation) {
                                if (description === undefined) {
                                    throw new _ErrorFromName("WinJS.UI.ListView.InvalidItem", strings.listViewInvalidItem);
                                }
                            }

                            itemPromise = this._dataSource.itemFromDescription(description);
                        }

                        return itemPromise.then(function (item) {
                            return positionItemAtIndex(item.index);
                        });
                    }
                },

                _endZoom: function (isCurrentView) {
                    if (this._isZombie()) {
                        return;
                    }

                    // Crop the content again and re-enable the scrollbar
                    if (!_BaseUtils.isPhone) {
                        var scrollOffset = this._canvasStart;

                        _ElementUtilities.removeClass(this._viewport, _Constants._zoomingYClass);
                        _ElementUtilities.removeClass(this._viewport, _Constants._zoomingXClass);
                        this._canvasStart = 0;
                        this._viewportScrollPosition = -scrollOffset;
                    }
                    this._disableEntranceAnimation = !isCurrentView;
                    this._isCurrentZoomView = isCurrentView;
                    this._zooming = false;
                    this._view.realizePage(this.scrollPosition, false);
                },

                _getItemOffsetPosition: function (index) {
                    var that = this;
                    return this._getItemOffset({ type: _UI.ObjectType.item, index: index }).then(function (position) {
                        return that._ensureFirstColumnRange(_UI.ObjectType.item).then(function () {
                            position = that._correctRangeInFirstColumn(position, _UI.ObjectType.item);
                            position = that._convertFromCanvasCoordinates(position);
                            if (that._horizontal()) {
                                position.left = position.begin;
                                position.width = position.end - position.begin;
                                position.height = position.totalHeight;
                            } else {
                                position.top = position.begin;
                                position.height = position.end - position.begin;
                                position.width = position.totalWidth;
                            }
                            return position;
                        });
                    });
                },

                _groupRemoved: function (key) {
                    this._groupFocusCache.deleteGroup(key);
                },

                _updateFocusCache: function (itemIndex) {
                    if (this._updateFocusCacheItemRequest) {
                        this._updateFocusCacheItemRequest.cancel();
                    }

                    var that = this;
                    this._updateFocusCacheItemRequest = this._view.items.requestItem(itemIndex).then(function () {
                        that._updateFocusCacheItemRequest = null;
                        var itemData = that._view.items.itemDataAt(itemIndex);
                        var groupIndex = that._groups.groupFromItem(itemIndex);
                        var groupKey = that._groups.group(groupIndex).key;
                        if (itemData.itemsManagerRecord.item) {
                            that._groupFocusCache.updateCache(groupKey, itemData.itemsManagerRecord.item.key, itemIndex);
                        }
                    });
                },

                _changeFocus: function (newFocus, skipSelection, ctrlKeyDown, skipEnsureVisible, keyboardFocused) {
                    if (this._isZombie()) {
                        return;
                    }
                    var targetItem;
                    if (newFocus.type !== _UI.ObjectType.groupHeader) {
                        targetItem = this._view.items.itemAt(newFocus.index);
                        if (!skipSelection && targetItem && _ElementUtilities.hasClass(targetItem, _Constants._nonSelectableClass)) {
                            skipSelection = true;
                        }
                        this._updateFocusCache(newFocus.index);
                    } else {
                        var group = this._groups.group(newFocus.index);
                        targetItem = group && group.header;
                    }
                    this._unsetFocusOnItem(!!targetItem);
                    this._hasKeyboardFocus = true;
                    this._selection._setFocused(newFocus, keyboardFocused);
                    if (!skipEnsureVisible) {
                        this.ensureVisible(newFocus);
                    }

                    // _selection.set() needs to know which item has focus so we
                    // must call it after _selection._setFocused() has been called.
                    if (!skipSelection && this._selectFocused(ctrlKeyDown)) {
                        this._selection.set(newFocus.index);
                    }
                    this._setFocusOnItem(newFocus);
                },

                // Updates ListView's internal focus state and, if ListView currently has focus, moves
                // Trident's focus to the item at index newFocus.
                // Similar to _changeFocus except _changeFocusPassively doesn't:
                // - ensure the item is selected or visible
                // - set Trident's focus to newFocus when ListView doesn't have focus
                _changeFocusPassively: function (newFocus) {
                    var targetItem;
                    if (newFocus.type !== _UI.ObjectType.groupHeader) {
                        targetItem = this._view.items.itemAt(newFocus.index);
                        this._updateFocusCache(newFocus.index);
                    } else {
                        var group = this._groups.group(newFocus.index);
                        targetItem = group && group.header;
                    }
                    this._unsetFocusOnItem(!!targetItem);
                    this._selection._setFocused(newFocus);
                    this._setFocusOnItem(newFocus);
                },

                _drawFocusRectangle: function (item) {
                    if (_ElementUtilities.hasClass(item, _Constants._headerClass)) {
                        _ElementUtilities.addClass(item, _Constants._itemFocusClass);
                    } else {
                        var itemBox = this._view.items.itemBoxFrom(item);
                        if (itemBox.querySelector("." + _Constants._itemFocusOutlineClass)) {
                            return;
                        }
                        _ElementUtilities.addClass(itemBox, _Constants._itemFocusClass);
                        var outline = _Global.document.createElement("div");
                        outline.className = _Constants._itemFocusOutlineClass;
                        itemBox.appendChild(outline);
                    }
                },

                _clearFocusRectangle: function (item) {
                    if (!item || this._isZombie()) {
                        return;
                    }

                    var itemBox = this._view.items.itemBoxFrom(item);
                    if (itemBox) {
                        _ElementUtilities.removeClass(itemBox, _Constants._itemFocusClass);
                        var outline = itemBox.querySelector("." + _Constants._itemFocusOutlineClass);
                        if (outline) {
                            outline.parentNode.removeChild(outline);
                        }
                    } else {
                        var header = this._groups.headerFrom(item);
                        if (header) {
                            _ElementUtilities.removeClass(header, _Constants._itemFocusClass);
                        }
                    }
                },

                _defaultInvoke: function (entity) {
                    if (this._isZoomedOut || (_BaseUtils.isPhone && this._triggerZoom && entity.type === _UI.ObjectType.groupHeader)) {
                        this._changeFocusPassively(entity);
                        this._triggerZoom();
                    }
                },

                _selectionAllowed: function ListView_selectionAllowed(itemIndex) {
                    var item = (itemIndex !== undefined ? this.elementFromIndex(itemIndex) : null),
                        itemSelectable = !(item && _ElementUtilities.hasClass(item, _Constants._nonSelectableClass));
                    return itemSelectable && this._selectionMode !== _UI.SelectionMode.none;
                },

                _multiSelection: function ListView_multiSelection() {
                    return this._selectionMode === _UI.SelectionMode.multi;
                },

                _selectOnTap: function ListView_selectOnTap() {
                    return this._tap === _UI.TapBehavior.toggleSelect || this._tap === _UI.TapBehavior.directSelect;
                },

                _selectFocused: function ListView_selectFocused(ctrlKeyDown) {
                    return this._tap === _UI.TapBehavior.directSelect && this._selectionMode === _UI.SelectionMode.multi && !ctrlKeyDown;
                },

                _dispose: function () {
                    if (!this._disposed) {
                        this._disposed = true;
                        var clear = function clear(e) {
                            e && (e.textContent = "");
                        };

                        _ElementUtilities._resizeNotifier.unsubscribe(this._element, this._onMSElementResizeBound);

                        this._batchingViewUpdates && this._batchingViewUpdates.cancel();

                        this._view && this._view._dispose && this._view._dispose();
                        this._mode && this._mode._dispose && this._mode._dispose();
                        this._groups && this._groups._dispose && this._groups._dispose();
                        this._selection && this._selection._dispose && this._selection._dispose();
                        this._layout && this._layout.uninitialize && this._layout.uninitialize();

                        this._itemsCountPromise && this._itemsCountPromise.cancel();
                        this._versionManager && this._versionManager._dispose();
                        this._clearInsertedItems();
                        this._itemsManager && this._itemsManager.release();
                        this._zoomAnimationPromise && this._zoomAnimationPromise.cancel();

                        clear(this._viewport);
                        clear(this._canvas);
                        clear(this._canvasProxy);

                        this._versionManager = null;
                        this._view = null;
                        this._mode = null;
                        this._element = null;
                        this._viewport = null;
                        this._itemsManager = null;
                        this._canvas = null;
                        this._canvasProxy = null;
                        this._itemsCountPromise = null;
                        this._scrollToFunctor = null;

                        var index = controlsToDispose.indexOf(this);
                        if (index >= 0) {
                            controlsToDispose.splice(index, 1);
                        }
                    }
                },

                _isZombie: function () {
                    // determines if this ListView is no longer in the DOM or has been cleared
                    //
                    return this._disposed || !(this.element.firstElementChild && _Global.document.body.contains(this.element));
                },

                _ifZombieDispose: function () {
                    var zombie = this._isZombie();
                    if (zombie && !this._disposed) {
                        scheduleForDispose(this);
                    }
                    return zombie;
                },

                _animationsDisabled: function () {
                    if (this._viewportWidth === 0 || this._viewportHeight === 0) {
                        return true;
                    }

                    return !_TransitionAnimation.isAnimationEnabled();
                },

                _fadeOutViewport: function ListView_fadeOutViewport() {
                    var that = this;
                    return new Promise(function (complete) {
                        if (that._animationsDisabled()) {
                            complete();
                            return;
                        }

                        if (!that._fadingViewportOut) {
                            if (that._waitingEntranceAnimationPromise) {
                                that._waitingEntranceAnimationPromise.cancel();
                                that._waitingEntranceAnimationPromise = null;
                            }
                            var eventDetails = that._fireAnimationEvent(ListViewAnimationType.contentTransition);
                            that._firedAnimationEvent = true;
                            var overflowStyle = _BaseUtils._browserStyleEquivalents["overflow-style"];
                            var animatedElement = overflowStyle ? that._viewport : that._canvas;
                            if (!eventDetails.prevented) {
                                that._fadingViewportOut = true;
                                if (overflowStyle) {
                                    animatedElement.style[overflowStyle.scriptName] = "none";
                                }
                                AnimationHelper.fadeOutElement(animatedElement).then(function () {
                                    if (that._isZombie()) { return; }
                                    that._fadingViewportOut = false;
                                    animatedElement.style.opacity = 1.0;
                                    complete();
                                });
                            } else {
                                that._disableEntranceAnimation = true;
                                animatedElement.style.opacity = 1.0;
                                complete();
                            }
                        }
                    });
                },

                _animateListEntrance: function (firstTime) {
                    var eventDetails = {
                        prevented: false,
                        animationPromise: Promise.wrap()
                    };
                    var that = this;
                    var overflowStyle = _BaseUtils._browserStyleEquivalents["overflow-style"];
                    var animatedElement = overflowStyle ? this._viewport : this._canvas;
                    function resetViewOpacity() {
                        that._canvas.style.opacity = 1;
                        if (overflowStyle) {
                            animatedElement.style[overflowStyle.scriptName] = "";
                        }
                    }

                    if (this._disableEntranceAnimation || this._animationsDisabled()) {
                        resetViewOpacity();
                        if (this._waitingEntranceAnimationPromise) {
                            this._waitingEntranceAnimationPromise.cancel();
                            this._waitingEntranceAnimationPromise = null;
                        }
                        return Promise.wrap();
                    }

                    if (!this._firedAnimationEvent) {
                        eventDetails = this._fireAnimationEvent(ListViewAnimationType.entrance);
                    } else {
                        this._firedAnimationEvent = false;
                    }

                    // The listview does not have an entrance animation on Phone
                    if (eventDetails.prevented || _BaseUtils.isPhone) {
                        resetViewOpacity();
                        return Promise.wrap();
                    } else {
                        if (this._waitingEntranceAnimationPromise) {
                            this._waitingEntranceAnimationPromise.cancel();
                        }
                        this._canvas.style.opacity = 0;
                        if (overflowStyle) {
                            animatedElement.style[overflowStyle.scriptName] = "none";
                        }
                        this._waitingEntranceAnimationPromise = eventDetails.animationPromise.then(function () {
                            if (!that._isZombie()) {
                                that._canvas.style.opacity = 1;
                                return AnimationHelper.animateEntrance(animatedElement, firstTime).then(function () {
                                    if (!that._isZombie()) {
                                        if (overflowStyle) {
                                            animatedElement.style[overflowStyle.scriptName] = "";
                                        }
                                        that._waitingEntranceAnimationPromise = null;
                                    }
                                });
                            }
                        });
                        return this._waitingEntranceAnimationPromise;
                    }
                },

                _fireAnimationEvent: function (type) {
                    var animationEvent = _Global.document.createEvent("CustomEvent"),
                        animationPromise = Promise.wrap();

                    animationEvent.initCustomEvent("contentanimating", true, true, {
                        type: type
                    });
                    if (type === ListViewAnimationType.entrance) {
                        animationEvent.detail.setPromise = function (delayPromise) {
                            animationPromise = delayPromise;
                        };
                    }
                    var prevented = !this._element.dispatchEvent(animationEvent);
                    return {
                        prevented: prevented,
                        animationPromise: animationPromise
                    };
                },

                // If they don't yet exist, create the start and end markers which are required
                // by Narrator's aria-flowto/flowfrom implementation. They mark the start and end
                // of ListView's set of out-of-order DOM elements and so they must surround the
                // headers and groups in the DOM.
                _createAriaMarkers: function ListView_createAriaMarkers() {
                    if (!this._viewport.getAttribute("aria-label")) {
                        this._viewport.setAttribute("aria-label", strings.listViewViewportAriaLabel);
                    }

                    if (!this._ariaStartMarker) {
                        this._ariaStartMarker = _Global.document.createElement("div");
                        this._ariaStartMarker.id = uniqueID(this._ariaStartMarker);
                        this._viewport.insertBefore(this._ariaStartMarker, this._viewport.firstElementChild);
                    }
                    if (!this._ariaEndMarker) {
                        this._ariaEndMarker = _Global.document.createElement("div");
                        this._ariaEndMarker.id = uniqueID(this._ariaEndMarker);
                        this._viewport.appendChild(this._ariaEndMarker);
                    }
                },

                // If the ListView is in static mode, then the roles of the list and items should be "list" and "listitem", respectively.
                // Otherwise, the roles should be "listbox" and "option." If the ARIA roles are out of sync with the ListView's
                // static/interactive state, update the role of the ListView and the role of each realized item.
                _updateItemsAriaRoles: function ListView_updateItemsAriaRoles() {
                    var that = this;
                    var listRole = this._element.getAttribute("role"),
                        expectedListRole,
                        expectedItemRole;

                    if (this._currentMode().staticMode()) {
                        expectedListRole = "list";
                        expectedItemRole = "listitem";
                    } else {
                        expectedListRole = "listbox";
                        expectedItemRole = "option";
                    }

                    if (listRole !== expectedListRole || this._itemRole !== expectedItemRole) {
                        this._element.setAttribute("role", expectedListRole);
                        this._itemRole = expectedItemRole;
                        this._view.items.each(function (index, itemElement) {
                            itemElement.setAttribute("role", that._itemRole);
                        });
                    }
                },

                _updateGroupHeadersAriaRoles: function ListView_updateGroupHeadersAriaRoles() {
                    var headerRole = (this.groupHeaderTapBehavior === _UI.GroupHeaderTapBehavior.none ? "separator" : "link");
                    if (this._headerRole !== headerRole) {
                        this._headerRole = headerRole;
                        for (var i = 0, len = this._groups.length() ; i < len; i++) {
                            var header = this._groups.group(i).header;
                            if (header) {
                                header.setAttribute("role", this._headerRole);
                            }
                        }
                    }
                },

                // Avoids unnecessary UIA selection events by only updating aria-selected if it has changed
                _setAriaSelected: function ListView_setAriaSelected(itemElement, isSelected) {
                    var ariaSelected = (itemElement.getAttribute("aria-selected") === "true");

                    if (isSelected !== ariaSelected) {
                        itemElement.setAttribute("aria-selected", isSelected);
                    }
                },

                _setupAriaSelectionObserver: function ListView_setupAriaSelectionObserver(item) {
                    if (!item._mutationObserver) {
                        this._mutationObserver.observe(item, { attributes: true, attributeFilter: ["aria-selected"] });
                        item._mutationObserver = true;
                    }
                },

                _itemPropertyChange: function ListView_itemPropertyChange(list) {
                    if (this._isZombie()) { return; }

                    var that = this;
                    var singleSelection = that._selectionMode === _UI.SelectionMode.single;
                    var changedItems = [];
                    var unselectableItems = [];

                    function revertAriaSelected(items) {
                        items.forEach(function (entry) {
                            entry.item.setAttribute("aria-selected", !entry.selected);
                        });
                    }

                    for (var i = 0, len = list.length; i < len; i++) {
                        var item = list[i].target;
                        var itemBox = that._view.items.itemBoxFrom(item);
                        var selected = item.getAttribute("aria-selected") === "true";

                        // Only respond to aria-selected changes coming from UIA. This check
                        // relies on the fact that, in renderSelection, we update the selection
                        // visual before aria-selected.
                        if (itemBox && (selected !== _ElementUtilities._isSelectionRendered(itemBox))) {
                            var index = that._view.items.index(itemBox);
                            var entry = { index: index, item: item, selected: selected };
                            (that._selectionAllowed(index) ? changedItems : unselectableItems).push(entry);
                        }
                    }
                    if (changedItems.length > 0) {
                        var signal = new _Signal();
                        that.selection._synchronize(signal).then(function () {
                            var newSelection = that.selection._cloneSelection();

                            changedItems.forEach(function (entry) {
                                if (entry.selected) {
                                    newSelection[singleSelection ? "set" : "add"](entry.index);
                                } else {
                                    newSelection.remove(entry.index);
                                }
                            });

                            return that.selection._set(newSelection);
                        }).then(function (approved) {
                            if (!that._isZombie() && !approved) {
                                // A selectionchanging event handler rejected the selection change
                                revertAriaSelected(changedItems);
                            }

                            signal.complete();
                        });
                    }

                    revertAriaSelected(unselectableItems);
                },

                _groupsEnabled: function () {
                    return !!this._groups.groupDataSource;
                },

                _getItemPosition: function ListView_getItemPosition(entity, preserveItemsBlocks) {
                    var that = this;
                    return this._view.waitForEntityPosition(entity).then(function () {
                        var container = (entity.type === _UI.ObjectType.groupHeader ? that._view._getHeaderContainer(entity.index) : that._view.getContainer(entity.index));
                        if (container) {
                            that._writeProfilerMark("WinJS.UI.ListView:getItemPosition,info");
                            var itemsBlockFrom;
                            var itemsBlockTo;
                            if (that._view._expandedRange) {
                                itemsBlockFrom = that._view._expandedRange.first.index;
                                itemsBlockTo = that._view._expandedRange.last.index;
                            } else {
                                preserveItemsBlocks = false;
                            }

                            if (entity.type === _UI.ObjectType.item) {
                                preserveItemsBlocks = !!preserveItemsBlocks;
                                preserveItemsBlocks &= that._view._ensureContainerInDOM(entity.index);
                            } else {
                                preserveItemsBlocks = false;
                            }

                            var margins = that._getItemMargins(entity.type),
                                position = {
                                    left: (that._rtl() ? getOffsetRight(container) - margins.right : container.offsetLeft - margins.left),
                                    top: container.offsetTop - margins.top,
                                    totalWidth: _ElementUtilities.getTotalWidth(container),
                                    totalHeight: _ElementUtilities.getTotalHeight(container),
                                    contentWidth: _ElementUtilities.getContentWidth(container),
                                    contentHeight: _ElementUtilities.getContentHeight(container)
                                };

                            if (preserveItemsBlocks) {
                                that._view._forceItemsBlocksInDOM(itemsBlockFrom, itemsBlockTo + 1);
                            }

                            // When a translation is applied to the surface during zooming, offsetLeft includes the canvas margins, so the left/top position will already be in canvas coordinates.
                            // If we're not zooming, we need to convert the position to canvas coordinates before returning.
                            return (that._zooming && that._canvasStart !== 0 ? position : that._convertToCanvasCoordinates(position));
                        } else {
                            return Promise.cancel;
                        }
                    });
                },

                _getItemOffset: function ListView_getItemOffset(entity, preserveItemsBlocks) {
                    var that = this;
                    return this._getItemPosition(entity, preserveItemsBlocks).then(function (pos) {
                        // _getItemOffset also includes the right/bottom margin of the previous row/column of items, so that ensureVisible/indexOfFirstVisible will jump such that
                        // the previous row/column is directly offscreen of the target item.
                        var margins = that._getItemMargins(entity.type);
                        if (that._horizontal()) {
                            var rtl = that._rtl();
                            pos.begin = pos.left - margins[rtl ? "left" : "right"];
                            pos.end = pos.left + pos.totalWidth + margins[rtl ? "right" : "left"];
                        } else {
                            pos.begin = pos.top - margins.bottom;
                            pos.end = pos.top + pos.totalHeight + margins.top;
                        }
                        return pos;
                    });
                },

                _getItemMargins: function ListView_getItemMargins(type) {
                    type = type || _UI.ObjectType.item;
                    var that = this;
                    var calculateMargins = function (className) {
                        var item = that._canvas.querySelector("." + className),
                                cleanup;

                        if (!item) {
                            item = _Global.document.createElement("div"),
                            _ElementUtilities.addClass(item, className);
                            that._viewport.appendChild(item);

                            cleanup = true;
                        }

                        var margins = _Layouts._getMargins(item);

                        if (cleanup) {
                            that._viewport.removeChild(item);
                        }
                        return margins;
                    };

                    if (type !== _UI.ObjectType.groupHeader) {
                        return (this._itemMargins ? this._itemMargins : (this._itemMargins = calculateMargins(_Constants._containerClass)));
                    } else {
                        return (this._headerMargins ? this._headerMargins : (this._headerMargins = calculateMargins(_Constants._headerContainerClass)));
                    }
                },

                _fireAccessibilityAnnotationCompleteEvent: function ListView_fireAccessibilityAnnotationCompleteEvent(firstIndex, lastIndex, firstHeaderIndex, lastHeaderIndex) {
                    // This event is fired in these cases:
                    // - When the data source count is 0, it is fired after the aria markers have been
                    //   updated. The event detail will be { firstIndex: -1, lastIndex: -1 }.
                    // - When the data source count is non-zero, it is fired after the aria markers
                    //   have been updated and the deferred work for the aria properties on the items
                    //   has completed.
                    // - When an item gets focus. The event will be { firstIndex: indexOfItem, lastIndex: indexOfItem }.
                    var detail = {
                        firstIndex: firstIndex,
                        lastIndex: lastIndex,
                        firstHeaderIndex: (+firstHeaderIndex) || -1,
                        lastHeaderIndex: (+lastHeaderIndex) || -1
                    };
                    var eventObject = _Global.document.createEvent("CustomEvent");
                    eventObject.initCustomEvent("accessibilityannotationcomplete", true, false, detail);
                    this._element.dispatchEvent(eventObject);
                },

                _ensureFirstColumnRange: function ListView_ensureFirstColumnRange(type) {
                    var propName = (type === _UI.ObjectType.item ? "_firstItemRange" : "_firstHeaderRange");
                    if (!this[propName]) {
                        var that = this;
                        return this._getItemOffset({ type: type, index: 0 }, true).then(function (firstRange) {
                            that[propName] = firstRange;
                        });
                    } else {
                        return Promise.wrap();
                    }
                },

                _correctRangeInFirstColumn: function ListView_correctRangeInFirstColumn(range, type) {
                    var firstRange = (type === _UI.ObjectType.groupHeader ? this._firstHeaderRange : this._firstItemRange);
                    if (firstRange.begin === range.begin) {
                        if (this._horizontal()) {
                            range.begin = -this._getCanvasMargins()[this._rtl() ? "right" : "left"];
                        } else {
                            range.begin = -this._getCanvasMargins().top;
                        }
                    }
                    return range;
                },

                _updateContainers: function ListView_updateContainers(groups, count, containersDelta, modifiedElements) {
                    var that = this;

                    var maxContainers = this._view.containers.length + (containersDelta > 0 ? containersDelta : 0);

                    var newTree = [];
                    var newKeyToGroupIndex = {};
                    var newContainers = [];
                    var removedContainers = [];

                    function createContainer() {
                        var element = _Global.document.createElement("div");
                        element.className = _Constants._containerClass;
                        return element;
                    }

                    function updateExistingGroupWithBlocks(groupNode, firstItem, newSize) {
                        if (firstItem + newSize > maxContainers) {
                            newSize = maxContainers - firstItem;
                        }

                        var itemsContainer = groupNode.itemsContainer,
                            blocks = itemsContainer.itemsBlocks,
                            lastBlock = blocks.length ? blocks[blocks.length - 1] : null,
                            currentSize = blocks.length ? (blocks.length - 1) * that._view._blockSize + lastBlock.items.length : 0,
                            delta = newSize - currentSize,
                            oldSize, children;

                        if (delta > 0) {
                            if (lastBlock && lastBlock.items.length < that._view._blockSize) {
                                var toAdd = Math.min(delta, that._view._blockSize - lastBlock.items.length);
                                _SafeHtml.insertAdjacentHTMLUnsafe(lastBlock.element, "beforeend", _Helpers._repeat("<div class='win-container win-backdrop'></div>", toAdd));

                                oldSize = lastBlock.items.length;
                                children = lastBlock.element.children;

                                for (var j = 0; j < toAdd; j++) {
                                    lastBlock.items.push(children[oldSize + j]);
                                }

                                delta -= toAdd;
                            }

                            var blocksCount = Math.floor(delta / that._view._blockSize),
                                lastBlockSize = delta % that._view._blockSize;

                            var blockMarkup = "<div class='win-itemsblock'>" + _Helpers._repeat("<div class='win-container win-backdrop'></div>", that._view._blockSize) + "</div>",
                                markup = _Helpers._repeat(blockMarkup, blocksCount);

                            if (lastBlockSize) {
                                markup += "<div class='win-itemsblock'>" + _Helpers._repeat("<div class='win-container win-backdrop'></div>", lastBlockSize) + "</div>";
                                blocksCount++;
                            }

                            var blocksTemp = _Global.document.createElement("div");
                            _SafeHtml.setInnerHTMLUnsafe(blocksTemp, markup);

                            var children = blocksTemp.children;
                            for (var j = 0; j < blocksCount; j++) {
                                var block = children[j],
                                    blockNode = {
                                        element: block,
                                        items: _Helpers._nodeListToArray(block.children)
                                    };
                                itemsContainer.itemsBlocks.push(blockNode);
                            }
                        } else if (delta < 0) {
                            for (var n = delta; n < 0; n++) {

                                var container = lastBlock.items.pop();

                                if (!that._view._requireFocusRestore && container.contains(_Global.document.activeElement)) {
                                    that._view._requireFocusRestore = _Global.document.activeElement;
                                    that._unsetFocusOnItem();
                                }

                                lastBlock.element.removeChild(container);
                                removedContainers.push(container);

                                if (!lastBlock.items.length) {
                                    if (itemsContainer.element === lastBlock.element.parentNode) {
                                        itemsContainer.element.removeChild(lastBlock.element);
                                    }

                                    blocks.pop();
                                    lastBlock = blocks[blocks.length - 1];
                                }
                            }
                        }

                        for (var j = 0, len = blocks.length; j < len; j++) {
                            var block = blocks[j];
                            for (var n = 0; n < block.items.length; n++) {
                                newContainers.push(block.items[n]);
                            }
                        }
                    }

                    function addInserted(groupNode, firstItemIndex, newSize) {
                        var added = modifiedElements.filter(function (entry) {
                            return (entry.oldIndex === -1 && entry.newIndex >= firstItemIndex && entry.newIndex < (firstItemIndex + newSize));
                        }).sort(function (left, right) {
                            return left.newIndex - right.newIndex;
                        });

                        var itemsContainer = groupNode.itemsContainer;

                        for (var i = 0, len = added.length; i < len; i++) {
                            var entry = added[i],
                                offset = entry.newIndex - firstItemIndex;

                            var container = createContainer(),
                                next = offset < itemsContainer.items.length ? itemsContainer.items[offset] : null;
                            itemsContainer.items.splice(offset, 0, container);
                            itemsContainer.element.insertBefore(container, next);
                        }
                    }

                    function updateExistingGroup(groupNode, firstItem, newSize) {
                        if (firstItem + newSize > maxContainers) {
                            newSize = maxContainers - firstItem;
                        }

                        var itemsContainer = groupNode.itemsContainer,
                            delta = newSize - itemsContainer.items.length;

                        if (delta > 0) {
                            var children = itemsContainer.element.children,
                                oldSize = children.length;

                            _SafeHtml.insertAdjacentHTMLUnsafe(itemsContainer.element, "beforeend", _Helpers._repeat("<div class='win-container win-backdrop'></div>", delta));

                            for (var n = 0; n < delta; n++) {
                                var container = children[oldSize + n];
                                itemsContainer.items.push(container);
                            }
                        }

                        for (var n = delta; n < 0; n++) {
                            var container = itemsContainer.items.pop();
                            itemsContainer.element.removeChild(container);
                            removedContainers.push(container);
                        }

                        for (var n = 0, len = itemsContainer.items.length; n < len; n++) {
                            newContainers.push(itemsContainer.items[n]);
                        }
                    }

                    function addNewGroup(groupInfo, firstItem) {
                        var header = that._view._createHeaderContainer(prevElement);

                        var groupNode = {
                            header: header,
                            itemsContainer: {
                                element: that._view._createItemsContainer(header),
                            }
                        };

                        groupNode.itemsContainer[that._view._blockSize ? "itemsBlocks" : "items"] = [];

                        if (that._view._blockSize) {
                            updateExistingGroupWithBlocks(groupNode, firstItem, groupInfo.size);
                        } else {
                            updateExistingGroup(groupNode, firstItem, groupInfo.size);
                        }

                        return groupNode;
                    }

                    function shift(groupNode, oldFirstItemIndex, currentFirstItemIndex, newSize) {
                        var currentLast = currentFirstItemIndex + newSize - 1,
                            firstShifted,
                            delta;

                        for (var i = 0, len = modifiedElements.length; i < len; i++) {
                            var entry = modifiedElements[i];
                            if (entry.newIndex >= currentFirstItemIndex && entry.newIndex <= currentLast && entry.oldIndex !== -1) {
                                if (firstShifted !== +firstShifted || entry.newIndex < firstShifted) {
                                    firstShifted = entry.newIndex;
                                    delta = entry.newIndex - entry.oldIndex;
                                }
                            }
                        }

                        if (firstShifted === +firstShifted) {
                            var addedBeforeShift = 0;
                            for (i = 0, len = modifiedElements.length; i < len; i++) {
                                var entry = modifiedElements[i];
                                if (entry.newIndex >= currentFirstItemIndex && entry.newIndex < firstShifted && entry.oldIndex === -1) {
                                    addedBeforeShift++;
                                }
                            }
                            var removedBeforeShift = 0,
                                oldFirstShifted = firstShifted - delta;
                            for (i = 0, len = modifiedElements.length; i < len; i++) {
                                var entry = modifiedElements[i];
                                if (entry.oldIndex >= oldFirstItemIndex && entry.oldIndex < oldFirstShifted && entry.newIndex === -1) {
                                    removedBeforeShift++;
                                }
                            }

                            delta += removedBeforeShift;
                            delta -= addedBeforeShift;
                            delta -= currentFirstItemIndex - oldFirstItemIndex;

                            var itemsContainer = groupNode.itemsContainer;

                            if (delta > 0) {
                                var children = itemsContainer.element.children;

                                _SafeHtml.insertAdjacentHTMLUnsafe(itemsContainer.element, "afterBegin", _Helpers._repeat("<div class='win-container win-backdrop'></div>", delta));

                                for (var n = 0; n < delta; n++) {
                                    var container = children[n];
                                    itemsContainer.items.splice(n, 0, container);
                                }
                            }

                            for (var n = delta; n < 0; n++) {
                                var container = itemsContainer.items.shift();
                                itemsContainer.element.removeChild(container);
                            }

                            if (delta) {
                                // Invalidate the layout of the entire group because we do not know the exact indices which were added/modified since they were before the realization range.
                                that._affectedRange.add({
                                    start: currentFirstItemIndex,
                                    end: currentFirstItemIndex + newSize
                                }, count);
                            }
                        }
                    }

                    function flatIndexToGroupIndex(index) {
                        var firstItem = 0;
                        for (var i = 0, len = that._view.tree.length; i < len; i++) {
                            var group = that._view.tree[i],
                                size = group.itemsContainer.items.length,
                                lastItem = firstItem + size - 1;

                            if (index >= firstItem && index <= lastItem) {
                                return {
                                    group: i,
                                    item: index - firstItem
                                };
                            }

                            firstItem += size;
                        }
                    }

                    var oldFirstItem = [];
                    var firstItem = 0;
                    if (!that._view._blockSize) {
                        for (var i = 0, len = this._view.tree.length; i < len; i++) {
                            oldFirstItem.push(firstItem);
                            firstItem += this._view.tree[i].itemsContainer.items.length;
                        }
                    }

                    if (!that._view._blockSize) {
                        var removed = modifiedElements.filter(function (entry) {
                            return entry.newIndex === -1 && !entry._removalHandled;
                        }).sort(function (left, right) {
                            return right.oldIndex - left.oldIndex;
                        });

                        for (var i = 0, len = removed.length; i < len; i++) {
                            var entry = removed[i];
                            entry._removalHandled = true;
                            var itemBox = entry._itemBox;
                            entry._itemBox = null;

                            var groupIndex = flatIndexToGroupIndex(entry.oldIndex);
                            var group = this._view.tree[groupIndex.group];

                            var container = group.itemsContainer.items[groupIndex.item];
                            container.parentNode.removeChild(container);

                            if (_ElementUtilities.hasClass(itemBox, _Constants._selectedClass)) {
                                _ElementUtilities.addClass(container, _Constants._selectedClass);
                            }

                            group.itemsContainer.items.splice(groupIndex.item, 1);

                            entry.element = container;
                        }
                    }

                    this._view._modifiedGroups = [];

                    var prevElement = this._canvasProxy;
                    firstItem = 0;
                    // When groups are disabled, loop thru all of the groups (there's only 1).
                    // When groups are enabled, loop until either we exhaust all of the groups in the data source
                    // or we exhaust all of the containers that have been created so far.
                    for (var i = 0, len = groups.length; i < len && (!this._groupsEnabled() || firstItem < maxContainers) ; i++) {
                        var groupInfo = groups[i],
                            existingGroupIndex = this._view.keyToGroupIndex[groupInfo.key],
                            existingGroup = this._view.tree[existingGroupIndex];

                        if (existingGroup) {
                            if (that._view._blockSize) {
                                updateExistingGroupWithBlocks(existingGroup, firstItem, groupInfo.size);
                            } else {
                                shift(existingGroup, oldFirstItem[existingGroupIndex], firstItem, groupInfo.size);
                                addInserted(existingGroup, firstItem, groupInfo.size);
                                updateExistingGroup(existingGroup, firstItem, groupInfo.size);
                            }
                            newTree.push(existingGroup);
                            newKeyToGroupIndex[groupInfo.key] = newTree.length - 1;
                            delete this._view.keyToGroupIndex[groupInfo.key];

                            prevElement = existingGroup.itemsContainer.element;

                            this._view._modifiedGroups.push({
                                oldIndex: existingGroupIndex,
                                newIndex: newTree.length - 1,
                                element: existingGroup.header
                            });
                        } else {
                            var newGroup = addNewGroup(groupInfo, firstItem);
                            newTree.push(newGroup);
                            newKeyToGroupIndex[groupInfo.key] = newTree.length - 1;

                            this._view._modifiedGroups.push({
                                oldIndex: -1,
                                newIndex: newTree.length - 1,
                                element: newGroup.header
                            });

                            prevElement = newGroup.itemsContainer.element;
                        }
                        firstItem += groupInfo.size;
                    }

                    var removedItemsContainers = [],
                        removedHeaders = [],
                        removedGroups = this._view.keyToGroupIndex ? Object.keys(this._view.keyToGroupIndex) : [];

                    for (var i = 0, len = removedGroups.length; i < len; i++) {
                        var groupIndex = this._view.keyToGroupIndex[removedGroups[i]],
                            groupNode = this._view.tree[groupIndex];

                        removedHeaders.push(groupNode.header);
                        removedItemsContainers.push(groupNode.itemsContainer.element);

                        if (this._view._blockSize) {
                            for (var b = 0; b < groupNode.itemsContainer.itemsBlocks.length; b++) {
                                var block = groupNode.itemsContainer.itemsBlocks[b];
                                for (var n = 0; n < block.items.length; n++) {
                                    removedContainers.push(block.items[n]);
                                }
                            }
                        } else {
                            for (var n = 0; n < groupNode.itemsContainer.items.length; n++) {
                                removedContainers.push(groupNode.itemsContainer.items[n]);
                            }
                        }

                        this._view._modifiedGroups.push({
                            oldIndex: groupIndex,
                            newIndex: -1,
                            element: groupNode.header
                        });
                    }

                    for (var i = 0, len = modifiedElements.length; i < len; i++) {
                        if (modifiedElements[i].newIndex === -1 && !modifiedElements[i]._removalHandled) {
                            modifiedElements[i]._removalHandled = true;
                            var itemBox = modifiedElements[i]._itemBox;
                            modifiedElements[i]._itemBox = null;
                            var container;
                            if (removedContainers.length) {
                                container = removedContainers.pop();
                                _ElementUtilities.empty(container);
                            } else {
                                container = createContainer();
                            }
                            if (_ElementUtilities.hasClass(itemBox, _Constants._selectedClass)) {
                                _ElementUtilities.addClass(container, _Constants._selectedClass);
                            }
                            container.appendChild(itemBox);
                            modifiedElements[i].element = container;
                        }
                    }

                    this._view.tree = newTree;
                    this._view.keyToGroupIndex = newKeyToGroupIndex;
                    this._view.containers = newContainers;

                    return {
                        removedHeaders: removedHeaders,
                        removedItemsContainers: removedItemsContainers
                    };
                },

                _writeProfilerMark: function ListView_writeProfilerMark(text) {
                    var message = "WinJS.UI.ListView:" + this._id + ":" + text;
                    _WriteProfilerMark(message);
                    _Log.log && _Log.log(message, null, "listviewprofiler");
                }
            }, {
                // Static members

                triggerDispose: function () {
                    /// <signature helpKeyword="WinJS.UI.ListView.triggerDispose">
                    /// <summary locid="WinJS.UI.ListView.triggerDispose">
                    /// Triggers the ListView disposal service manually. In normal operation this is triggered
                    /// at ListView instantiation. However in some scenarios it may be appropriate to run
                    /// the disposal service manually.
                    /// </summary>
                    /// </signature>
                    disposeControls();
                }

            });
            _Base.Class.mix(ListView, _Events.createEventProperties(
                "iteminvoked",
                "groupheaderinvoked",
                "selectionchanging",
                "selectionchanged",
                "loadingstatechanged",
                "keyboardnavigating",
                "contentanimating",
                "itemdragstart",
                "itemdragenter",
                "itemdragend",
                "itemdragbetween",
                "itemdragleave",
                "itemdragchanged",
                "itemdragdrop",
                "accessibilityannotationcomplete"));
            _Base.Class.mix(ListView, _Control.DOMEventMixin);
            return ListView;
        })
    });

});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/FlipView/_Constants',[
    ], function constantsInit() {
    "use strict";

    var members = {};

    members.datasourceCountChangedEvent = "datasourcecountchanged";
    members.pageVisibilityChangedEvent = "pagevisibilitychanged";
    members.pageSelectedEvent = "pageselected";
    members.pageCompletedEvent = "pagecompleted";

    return members;
});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

define('WinJS/Controls/FlipView/_PageManager',[
    'exports',
    '../../Core/_Global',
    '../../Core/_Base',
    '../../Core/_BaseUtils',
    '../../Core/_ErrorFromName',
    '../../Core/_Log',
    '../../Core/_Resources',
    '../../Core/_WriteProfilerMark',
    '../../Animations',
    '../../Promise',
    '../../_Signal',
    '../../Scheduler',
    '../../Utilities/_Dispose',
    '../../Utilities/_ElementUtilities',
    '../../Utilities/_TabContainer',
    './_Constants'
    ], function flipperPageManagerInit(exports, _Global, _Base, _BaseUtils, _ErrorFromName, _Log, _Resources, _WriteProfilerMark, Animations, Promise, _Signal, Scheduler, _Dispose, _ElementUtilities, _TabContainer, _Constants) {
    "use strict";

    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {

        // Definition of our private utility
        _FlipPageManager: _Base.Namespace._lazy(function () {
            var uniqueID = _ElementUtilities._uniqueID;
            var styleEquivalents = _BaseUtils._browserStyleEquivalents;

            var leftBufferAmount = 50,
                itemSelectedEventDelay = 250;

            var strings = {
                get badCurrentPage() { return "Invalid argument: currentPage must be a number greater than or equal to zero and be within the bounds of the datasource"; }
            };

            function isFlipper(element) {
                var control = element.winControl;
                if (control && control._isFlipView) {
                    return true;
                }

                return false;
            }

            function flipperPropertyChanged(list) {
                list.forEach(function (record) {
                    var element = record.target;
                    if (element.winControl && element.tabIndex >= 0) {
                        element.winControl._pageManager._updateTabIndex(element.tabIndex);
                        element.tabIndex = -1;
                    }
                    var that = element.winControl;
                    if (that && that._isFlipView) {
                        var dirChanged = false;
                        if (record.attributeName === "dir") {
                            dirChanged = true;
                        } else if (record.attributeName === "style") {
                            dirChanged = (that._cachedStyleDir !== element.style.direction);
                        }
                        if (dirChanged) {
                            that._cachedStyleDir = element.style.direction;
                            that._pageManager._rtl = _Global.getComputedStyle(that._pageManager._flipperDiv, null).direction === "rtl";
                            that._pageManager.resized();
                        }
                    }
                });
            }

            var _FlipPageManager = _Base.Class.define(function _FlipPageManager_ctor(flipperDiv, panningDiv, panningDivContainer, itemsManager, itemSpacing, environmentSupportsTouch, buttonVisibilityHandler) {
                // Construction
                this._visibleElements = [];
                this._flipperDiv = flipperDiv;
                this._panningDiv = panningDiv;
                this._panningDivContainer = panningDivContainer;
                this._buttonVisibilityHandler = buttonVisibilityHandler;
                this._currentPage = null;
                this._rtl = _Global.getComputedStyle(this._flipperDiv, null).direction === "rtl";
                this._itemsManager = itemsManager;
                this._itemSpacing = itemSpacing;
                this._tabIndex = _ElementUtilities.getTabIndex(flipperDiv);
                if (this._tabIndex < 0) {
                    this._tabIndex = 0;
                }
                panningDiv.tabIndex = -1;
                flipperDiv.tabIndex = -1;
                this._tabManager = new _TabContainer.TabContainer(this._panningDivContainer);
                this._tabManager.tabIndex = this._tabIndex;
                this._lastSelectedPage = null;
                this._lastSelectedElement = null;
                this._bufferSize = _FlipPageManager.flipPageBufferCount;
                this._cachedSize = -1;
                this._environmentSupportsTouch = environmentSupportsTouch;

                var that = this;
                this._panningDiv.addEventListener("keydown", function (event) {
                    if (that._blockTabs && event.keyCode === _ElementUtilities.Key.tab) {
                        event.stopImmediatePropagation();
                        event.preventDefault();
                    }
                }, true);
                _ElementUtilities._addEventListener(this._flipperDiv, "focusin", function (event) {
                    if (event.target === that._flipperDiv) {
                        if (that._currentPage.element) {
                            _ElementUtilities._setActive(that._currentPage.element);
                        }
                    }
                }, false);
                new _ElementUtilities._MutationObserver(flipperPropertyChanged).observe(this._flipperDiv, { attributes: true, attributeFilter: ["dir", "style", "tabindex"] });
                this._cachedStyleDir = this._flipperDiv.style.direction;

                this._handleManipulationStateChangedBound = this._handleManipulationStateChanged.bind(this);

                if (this._environmentSupportsTouch) {
                    this._panningDivContainer.addEventListener(_BaseUtils._browserEventEquivalents["manipulationStateChanged"], this._handleManipulationStateChangedBound, true);
                }
            }, {
                // Public Methods

                initialize: function (initialIndex, isHorizontal) {
                    var currPage = null;
                    // Every call to offsetWidth/offsetHeight causes an switch from Script to Layout which affects
                    // the performance of the control. The values will be cached and will be updated when a resize occurs.
                    this._panningDivContainerOffsetWidth = this._panningDivContainer.offsetWidth;
                    this._panningDivContainerOffsetHeight = this._panningDivContainer.offsetHeight;
                    this._isHorizontal = isHorizontal;
                    if (!this._currentPage) {
                        this._bufferAriaStartMarker = _Global.document.createElement("div");
                        this._bufferAriaStartMarker.id = uniqueID(this._bufferAriaStartMarker);
                        this._panningDiv.appendChild(this._bufferAriaStartMarker);

                        this._currentPage = this._createFlipPage(null, this);
                        currPage = this._currentPage;
                        this._panningDiv.appendChild(currPage.pageRoot);

                        // flipPageBufferCount is added here twice.
                        // Once for the buffer prior to the current item, and once for the buffer ahead of the current item.
                        var pagesToInit = 2 * this._bufferSize;
                        for (var i = 0; i < pagesToInit; i++) {
                            currPage = this._createFlipPage(currPage, this);
                            this._panningDiv.appendChild(currPage.pageRoot);
                        }

                        this._bufferAriaEndMarker = _Global.document.createElement("div");
                        this._bufferAriaEndMarker.id = uniqueID(this._bufferAriaEndMarker);
                        this._panningDiv.appendChild(this._bufferAriaEndMarker);
                    }

                    this._prevMarker = this._currentPage.prev.prev;

                    if (this._itemsManager) {
                        this.setNewItemsManager(this._itemsManager, initialIndex);
                    }
                },

                dispose: function () {
                    var curPage = this._currentPage;

                    var tmpPage = curPage;
                    do {
                        _Dispose._disposeElement(tmpPage.element);
                        tmpPage = tmpPage.next;
                    } while (tmpPage !== curPage);
                },

                setOrientation: function (isHorizontal) {
                    if (this._notificationsEndedSignal) {
                        var that = this;
                        this._notificationsEndedSignal.promise.done(function () {
                            that._notificationsEndedSignal = null;
                            that.setOrientation(isHorizontal);
                        });
                        return;
                    }

                    if (isHorizontal === this._isHorizontal) {
                        return;
                    }

                    this._isOrientationChanging = true;

                    if (this._isHorizontal) {
                        _ElementUtilities.setScrollPosition(this._panningDivContainer, { scrollLeft: this._getItemStart(this._currentPage), scrollTop: 0 });
                    } else {
                        _ElementUtilities.setScrollPosition(this._panningDivContainer, { scrollLeft: 0, scrollTop: this._getItemStart(this._currentPage) });
                    }
                    this._isHorizontal = isHorizontal;

                    var containerStyle = this._panningDivContainer.style;
                    containerStyle.overflowX = "hidden";
                    containerStyle.overflowY = "hidden";

                    var that = this;
                    _BaseUtils._requestAnimationFrame(function () {
                        that._isOrientationChanging = false;
                        that._forEachPage(function (curr) {
                            var currStyle = curr.pageRoot.style;
                            currStyle.left = "0px";
                            currStyle.top = "0px";
                        });
                        containerStyle.overflowX = ((that._isHorizontal && that._environmentSupportsTouch) ? "scroll" : "hidden");
                        containerStyle.overflowY = ((that._isHorizontal || !that._environmentSupportsTouch) ? "hidden" : "scroll");
                        that._ensureCentered();
                    });
                },

                resetState: function (initialIndex) {
                    this._writeProfilerMark("WinJS.UI.FlipView:resetState,info");
                    if (initialIndex !== 0) {
                        var indexValid = this.jumpToIndex(initialIndex, true);
                        if (!indexValid && _BaseUtils.validation) {
                            throw new _ErrorFromName("WinJS.UI.FlipView.BadCurrentPage", strings.badCurrentPage);
                        }
                        return indexValid;
                    } else {
                        _Dispose.disposeSubTree(this._flipperDiv);
                        this._resetBuffer(null, true);
                        var that = this;
                        var work = Promise.wrap(true);
                        if (this._itemsManager) {
                            work = that._itemsManager._firstItem().then(function (e) {
                                that._currentPage.setElement(e);
                                return that._fetchPreviousItems(true).
                                    then(function () {
                                        return that._fetchNextItems();
                                    }).then(function () {
                                        that._setButtonStates();
                                    });
                            });
                        }
                        return work.then(function () {
                            that._tabManager.childFocus = that._currentPage.element;
                            that._ensureCentered();
                            that._itemSettledOn();
                        });
                    }
                },

                setNewItemsManager: function (manager, initialIndex) {
                    this._itemsManager = manager;
                    var that = this;
                    return this.resetState(initialIndex).then(function () {
                        // resetState already configures the tabManager, calls _ensureCentered and _itemSettledOn when the initial index is 0
                        if (initialIndex !== 0) {
                            that._tabManager.childFocus = that._currentPage.element;
                            that._ensureCentered();
                            that._itemSettledOn();
                        }
                    });
                },

                currentIndex: function () {
                    if (!this._itemsManager) {
                        return 0;
                    }
                    var index = 0;
                    var element = (this._navigationAnimationRecord ? this._navigationAnimationRecord.newCurrentElement : this._currentPage.element);
                    if (element) {
                        index = this._getElementIndex(element);
                    }
                    return index;
                },

                resetScrollPos: function () {
                    this._ensureCentered();
                },

                scrollPosChanged: function () {

                    if (this._hasFocus) {
                        this._hadFocus = true;
                    }

                    if (!this._itemsManager || !this._currentPage.element || this._isOrientationChanging) {
                        return;
                    }

                    var newPos = this._getViewportStart(),
                        bufferEnd = (this._lastScrollPos > newPos ? this._getTailOfBuffer() : this._getHeadOfBuffer());

                    if (newPos === this._lastScrollPos) {
                        return;
                    }

                    while (this._currentPage.element && this._getItemStart(this._currentPage) > newPos && this._currentPage.prev.element) {
                        this._currentPage = this._currentPage.prev;
                        this._fetchOnePrevious(bufferEnd.prev);
                        bufferEnd = bufferEnd.prev;
                    }

                    while (this._currentPage.element && this._itemEnd(this._currentPage) <= newPos && this._currentPage.next.element) {
                        this._currentPage = this._currentPage.next;
                        this._fetchOneNext(bufferEnd.next);
                        bufferEnd = bufferEnd.next;
                    }
                    this._setButtonStates();
                    this._checkElementVisibility(false);
                    this._blockTabs = true;
                    this._lastScrollPos = newPos;
                    if (this._currentPage.element) {
                        this._tabManager.childFocus = this._currentPage.element;
                    }
                    this._setListEnds();

                    if (!this._manipulationState && this._viewportOnItemStart()) {
                        // Setup a timeout to invoke _itemSettledOn in cases where the scroll position is changed, and the control
                        // does not know when it has settled on an item (e.g. 1-finger swipe with narrator touch).
                        this._currentPage.element.setAttribute("aria-setsize", this._cachedSize);
                        this._currentPage.element.setAttribute("aria-posinset", this.currentIndex() + 1);
                        this._timeoutPageSelection();
                    }
                },

                itemRetrieved: function (real, placeholder) {
                    var that = this;
                    this._forEachPage(function (curr) {
                        if (curr.element === placeholder) {
                            if (curr === that._currentPage || curr === that._currentPage.next) {
                                that._changeFlipPage(curr, placeholder, real);
                            } else {
                                curr.setElement(real, true);
                            }
                            return true;
                        }
                    });
                    if (this._navigationAnimationRecord && this._navigationAnimationRecord.elementContainers) {
                        var animatingElements = this._navigationAnimationRecord.elementContainers;
                        for (var i = 0, len = animatingElements.length; i < len; i++) {
                            if (animatingElements[i].element === placeholder) {
                                that._changeFlipPage(animatingElements[i], placeholder, real);
                                animatingElements[i].element = real;
                            }
                        }
                    }
                    this._checkElementVisibility(false);
                },

                resized: function () {
                    this._panningDivContainerOffsetWidth = this._panningDivContainer.offsetWidth;
                    this._panningDivContainerOffsetHeight = this._panningDivContainer.offsetHeight;
                    var that = this;
                    this._forEachPage(function (curr) {
                        curr.pageRoot.style.width = that._panningDivContainerOffsetWidth + "px";
                        curr.pageRoot.style.height = that._panningDivContainerOffsetHeight + "px";
                    });

                    // Call _ensureCentered to adjust all the width/height of the pages in the buffer
                    this._ensureCentered();
                    this._writeProfilerMark("WinJS.UI.FlipView:resize,StopTM");
                },

                jumpToIndex: function (index, forceJump) {
                    // If we force jumping to an index, we are not interested in making sure that there is distance
                    // between the current and the new index.
                    if (!forceJump) {
                        if (!this._itemsManager || !this._currentPage.element || index < 0) {
                            return Promise.wrap(false);
                        }

                        // If we have to keep our pages in memory, we need to iterate through every single item from our current position to the desired target
                        var currIndex = this._getElementIndex(this._currentPage.element),
                            distance = Math.abs(index - currIndex);

                        if (distance === 0) {
                            return Promise.wrap(false);
                        }
                    }

                    var tail = Promise.wrap(true);
                    var that = this;

                    tail = tail.then(function () {
                        var itemPromise = that._itemsManager._itemPromiseAtIndex(index);
                        return Promise.join({
                            element: that._itemsManager._itemFromItemPromise(itemPromise),
                            item: itemPromise
                        }).then(function (v) {
                            var elementAtIndex = v.element;

                            // Reset the buffer regardless of whether we have elementAtIndex or not
                            that._resetBuffer(elementAtIndex, forceJump);

                            if (!elementAtIndex) {
                                return false;
                            }

                            that._currentPage.setElement(elementAtIndex);
                            return that._fetchNextItems().
                                then(function () {
                                    return that._fetchPreviousItems(true);
                                }).
                                then(function () {
                                    return true;
                                });
                        });
                    });
                    tail = tail.then(function (v) {
                        that._setButtonStates();
                        return v;
                    });

                    return tail;
                },

                startAnimatedNavigation: function (goForward, cancelAnimationCallback, completionCallback) {
                    this._writeProfilerMark("WinJS.UI.FlipView:startAnimatedNavigation,info");
                    if (this._currentPage.element) {
                        var outgoingPage = this._currentPage,
                            incomingPage = (goForward ? this._currentPage.next : this._currentPage.prev);

                        if (incomingPage.element) {
                            if (this._hasFocus) {
                                // Give focus to the panning div ONLY if anything inside the flipview control currently has
                                // focus; otherwise, it will be lost when the current page is animated during the navigation.
                                _ElementUtilities._setActive(this._panningDiv);
                            }
                            this._navigationAnimationRecord = {};
                            this._navigationAnimationRecord.goForward = goForward;
                            this._navigationAnimationRecord.cancelAnimationCallback = cancelAnimationCallback;
                            this._navigationAnimationRecord.completionCallback = completionCallback;
                            this._navigationAnimationRecord.oldCurrentPage = outgoingPage;
                            this._navigationAnimationRecord.newCurrentPage = incomingPage;
                            var outgoingElement = outgoingPage.element;
                            var incomingElement = incomingPage.element;
                            this._navigationAnimationRecord.newCurrentElement = incomingElement;

                            // When a page element is animated during a navigation, it is temporarily appended on a different container during the animation (see _createDiscardablePage).
                            // However, updates in the data source can happen (change, remove, insert, etc) during the animation affecting the element that is being animated.
                            // Therefore, the page object also maintains the elementUniqueID, and the functions that deal with re-building the internal buffer (shifting/remove/etc)
                            // do all the comparissons, based on the page.elementUniqueID that way even if the element of the page is being animated, we are able to restore/discard it
                            // into the internal buffer back in the correct place.
                            outgoingPage.setElement(null, true);
                            outgoingPage.elementUniqueID = uniqueID(outgoingElement);
                            incomingPage.setElement(null, true);
                            incomingPage.elementUniqueID = uniqueID(incomingElement);

                            var outgoingFlipPage = this._createDiscardablePage(outgoingElement),
                                incomingFlipPage = this._createDiscardablePage(incomingElement);

                            outgoingFlipPage.pageRoot.itemIndex = this._getElementIndex(outgoingElement);
                            incomingFlipPage.pageRoot.itemIndex = outgoingFlipPage.pageRoot.itemIndex + (goForward ? 1 : -1);
                            outgoingFlipPage.pageRoot.style.position = "absolute";
                            incomingFlipPage.pageRoot.style.position = "absolute";
                            outgoingFlipPage.pageRoot.style.zIndex = 1;
                            incomingFlipPage.pageRoot.style.zIndex = 2;
                            this._setItemStart(outgoingFlipPage, 0);
                            this._setItemStart(incomingFlipPage, 0);
                            this._blockTabs = true;
                            this._visibleElements.push(incomingElement);
                            this._announceElementVisible(incomingElement);
                            this._navigationAnimationRecord.elementContainers = [outgoingFlipPage, incomingFlipPage];
                            return {
                                outgoing: outgoingFlipPage,
                                incoming: incomingFlipPage
                            };
                        }
                    }
                    return null;
                },

                endAnimatedNavigation: function (goForward, outgoing, incoming) {
                    this._writeProfilerMark("WinJS.UI.FlipView:endAnimatedNavigation,info");
                    if (this._navigationAnimationRecord &&
                        this._navigationAnimationRecord.oldCurrentPage &&
                        this._navigationAnimationRecord.newCurrentPage) {
                        var outgoingRemoved = this._restoreAnimatedElement(this._navigationAnimationRecord.oldCurrentPage, outgoing);
                        this._restoreAnimatedElement(this._navigationAnimationRecord.newCurrentPage, incoming);
                        if (!outgoingRemoved) {
                            // Advance only when the element in the current page was not removed because if it did, all the pages
                            // were shifted.
                            this._setViewportStart(this._getItemStart(goForward ? this._currentPage.next : this._currentPage.prev));
                        }
                        this._navigationAnimationRecord = null;
                        this._itemSettledOn();
                    }
                },

                startAnimatedJump: function (index, cancelAnimationCallback, completionCallback) {
                    this._writeProfilerMark("WinJS.UI.FlipView:startAnimatedJump,info");

                    if (this._hasFocus) {
                        this._hadFocus = true;
                    }
                    if (this._currentPage.element) {
                        var oldElement = this._currentPage.element;
                        var oldIndex = this._getElementIndex(oldElement);
                        var that = this;

                        return that.jumpToIndex(index).then(function (v) {
                            if (!v) {
                                return null;
                            }
                            that._navigationAnimationRecord = {};
                            that._navigationAnimationRecord.cancelAnimationCallback = cancelAnimationCallback;
                            that._navigationAnimationRecord.completionCallback = completionCallback;
                            that._navigationAnimationRecord.oldCurrentPage = null;
                            that._forEachPage(function (curr) {
                                if (curr.element === oldElement) {
                                    that._navigationAnimationRecord.oldCurrentPage = curr;
                                    return true;
                                }
                            });
                            that._navigationAnimationRecord.newCurrentPage = that._currentPage;
                            if (that._navigationAnimationRecord.newCurrentPage === that._navigationAnimationRecord.oldCurrentPage) {
                                return null;
                            }
                            var newElement = that._currentPage.element;
                            that._navigationAnimationRecord.newCurrentElement = newElement;

                            // When a page element is animated during a jump, it is temporarily appended on a different container during the animation (see _createDiscardablePage).
                            // However, updates in the data source can happen (change, remove, insert, etc) during the animation affecting the element that is being animated.
                            // Therefore, the page object also maintains the elementUniqueID, and the functions that deal with re-building the internal buffer (shifting/remove/etc)
                            // do all the comparissons, based on the page.elementUniqueID that way even if the element of the page is being animated, we are able to restore/discard it
                            // into the internal buffer back in the correct place.
                            that._currentPage.setElement(null, true);
                            that._currentPage.elementUniqueID = uniqueID(newElement);

                            if (that._navigationAnimationRecord.oldCurrentPage) {
                                that._navigationAnimationRecord.oldCurrentPage.setElement(null, true);
                            }

                            var oldFlipPage = that._createDiscardablePage(oldElement),
                                newFlipPage = that._createDiscardablePage(newElement);
                            oldFlipPage.pageRoot.itemIndex = oldIndex;
                            newFlipPage.pageRoot.itemIndex = index;
                            oldFlipPage.pageRoot.style.position = "absolute";
                            newFlipPage.pageRoot.style.position = "absolute";
                            oldFlipPage.pageRoot.style.zIndex = 1;
                            newFlipPage.pageRoot.style.zIndex = 2;
                            that._setItemStart(oldFlipPage, 0);
                            that._setItemStart(newFlipPage, that._itemSize(that._currentPage));
                            that._visibleElements.push(newElement);
                            that._announceElementVisible(newElement);
                            that._navigationAnimationRecord.elementContainers = [oldFlipPage, newFlipPage];
                            that._blockTabs = true;
                            return {
                                oldPage: oldFlipPage,
                                newPage: newFlipPage
                            };
                        });
                    }

                    return Promise.wrap(null);
                },

                simulateMouseWheelScroll: function (ev) {

                    if (this._environmentSupportsTouch || this._waitingForMouseScroll) {
                        return;
                    }

                    var wheelingForward;

                    if (typeof ev.deltaY === 'number') {
                        wheelingForward = (ev.deltaX || ev.deltaY) > 0;
                    } else {
                        wheelingForward = ev.wheelDelta < 0;
                    }

                    var targetPage = wheelingForward ? this._currentPage.next : this._currentPage.prev;

                    if (!targetPage.element) {
                        return;
                    }

                    var zoomToContent = { contentX: 0, contentY: 0, viewportX: 0, viewportY: 0 };
                    zoomToContent[this._isHorizontal ? "contentX" : "contentY"] = this._getItemStart(targetPage);
                    _ElementUtilities._zoomTo(this._panningDivContainer, zoomToContent);
                    this._waitingForMouseScroll = true;

                    // The 100ms is added to the zoom duration to give the snap feeling where the page sticks
                    // while scrolling
                    _Global.setTimeout(function () {
                        this._waitingForMouseScroll = false;
                    }.bind(this), _ElementUtilities._zoomToDuration + 100);
                },

                endAnimatedJump: function (oldCurr, newCurr) {
                    this._writeProfilerMark("WinJS.UI.FlipView:endAnimatedJump,info");
                    if (this._navigationAnimationRecord.oldCurrentPage) {
                        this._navigationAnimationRecord.oldCurrentPage.setElement(oldCurr.element, true);
                    } else {
                        if (oldCurr.element.parentNode) {
                            oldCurr.element.parentNode.removeChild(oldCurr.element);
                        }
                    }
                    this._navigationAnimationRecord.newCurrentPage.setElement(newCurr.element, true);
                    this._navigationAnimationRecord = null;
                    this._ensureCentered();
                    this._itemSettledOn();
                },

                inserted: function (element, prev, next, animateInsertion) {
                    this._writeProfilerMark("WinJS.UI.FlipView:inserted,info");
                    var curr = this._prevMarker,
                        passedCurrent = false,
                        elementSuccessfullyPlaced = false;

                    if (animateInsertion) {
                        this._createAnimationRecord(uniqueID(element), null);
                        this._getAnimationRecord(element).inserted = true;
                    }

                    if (!prev) {
                        if (!next) {
                            this._currentPage.setElement(element);
                        } else {
                            while (curr.next !== this._prevMarker && curr.elementUniqueID !== uniqueID(next)) {
                                if (curr === this._currentPage) {
                                    passedCurrent = true;
                                }
                                curr = curr.next;
                            }

                            if (curr.elementUniqueID === uniqueID(next) && curr !== this._prevMarker) {
                                curr.prev.setElement(element);
                                elementSuccessfullyPlaced = true;
                            } else {
                                this._releaseElementIfNotAnimated(element);
                            }
                        }
                    } else {
                        do {
                            if (curr === this._currentPage) {
                                passedCurrent = true;
                            }
                            if (curr.elementUniqueID === uniqueID(prev)) {
                                elementSuccessfullyPlaced = true;
                                var pageShifted = curr,
                                    lastElementMoved = element,
                                    lastElementMovedUniqueID = uniqueID(element),
                                    temp;
                                if (passedCurrent) {
                                    while (pageShifted.next !== this._prevMarker) {
                                        temp = pageShifted.next.element;
                                        lastElementMovedUniqueID = pageShifted.next.elementUniqueID;
                                        pageShifted.next.setElement(lastElementMoved, true);
                                        if (!lastElementMoved && lastElementMovedUniqueID) {
                                            // Shift the uniqueID of the page manually since its element is being animated.
                                            // This page  will not contain the element until the animation completes.
                                            pageShifted.next.elementUniqueID = lastElementMovedUniqueID;
                                        }
                                        lastElementMoved = temp;
                                        pageShifted = pageShifted.next;
                                    }
                                } else {
                                    if (curr.elementUniqueID === curr.next.elementUniqueID && curr.elementUniqueID) {
                                        pageShifted = curr.next;
                                    }
                                    while (pageShifted.next !== this._prevMarker) {
                                        temp = pageShifted.element;
                                        lastElementMovedUniqueID = pageShifted.elementUniqueID;
                                        pageShifted.setElement(lastElementMoved, true);
                                        if (!lastElementMoved && lastElementMovedUniqueID) {
                                            // Shift the uniqueID of the page manually since its element is being animated.
                                            // This page  will not contain the element until the animation completes.
                                            pageShifted.elementUniqueID = lastElementMovedUniqueID;
                                        }
                                        lastElementMoved = temp;
                                        pageShifted = pageShifted.prev;
                                    }
                                }
                                if (lastElementMoved) {
                                    var reused = false;
                                    this._forEachPage(function (curr) {
                                        if (uniqueID(lastElementMoved) === curr.elementUniqueID) {
                                            reused = true;
                                            return true;
                                        }
                                    });
                                    if (!reused) {
                                        this._releaseElementIfNotAnimated(lastElementMoved);
                                    }
                                }
                                break;
                            }
                            curr = curr.next;
                        } while (curr !== this._prevMarker);
                    }

                    this._getAnimationRecord(element).successfullyMoved = elementSuccessfullyPlaced;
                    this._setButtonStates();
                },

                changed: function (newVal, element) {
                    this._writeProfilerMark("WinJS.UI.FlipView:changed,info");
                    var that = this;
                    this._forEachPage(function (curr) {
                        if (curr.elementUniqueID === uniqueID(element)) {
                            var record = that._animationRecords[curr.elementUniqueID];
                            record.changed = true;
                            record.oldElement = element;
                            record.newElement = newVal;
                            curr.element = newVal; // We set curr's element field here so that next/prev works, but we won't update the visual until endNotifications
                            curr.elementUniqueID = uniqueID(newVal);
                            that._animationRecords[uniqueID(newVal)] = record;
                            return true;
                        }
                    });

                    if (this._navigationAnimationRecord && this._navigationAnimationRecord.elementContainers) {
                        for (var i = 0, len = this._navigationAnimationRecord.elementContainers.length; i < len; i++) {
                            var page = this._navigationAnimationRecord.elementContainers[i];
                            if (page && page.elementUniqueID === uniqueID(element)) {
                                page.element = newVal;
                                page.elementUniqueID = uniqueID(newVal);
                            }
                        }

                        var newElement = this._navigationAnimationRecord.newCurrentElement;
                        if (newElement && uniqueID(newElement) === uniqueID(element)) {
                            this._navigationAnimationRecord.newCurrentElement = newVal;
                        }
                    }
                },

                moved: function (element, prev, next) {
                    this._writeProfilerMark("WinJS.UI.FlipView:moved,info");
                    var record = this._getAnimationRecord(element);

                    if (!record) {
                        record = this._createAnimationRecord(uniqueID(element));
                    }

                    record.moved = true;
                    this.removed(element, false, false);
                    if (prev || next) {
                        this.inserted(element, prev, next, false);
                    } else {
                        record.successfullyMoved = false;
                    }
                },

                removed: function (element, mirage, animateRemoval) {
                    this._writeProfilerMark("WinJS.UI.FlipView:removed,info");
                    var that = this;
                    var prevMarker = this._prevMarker;
                    var work = Promise.wrap();

                    if (mirage) {
                        var clearNext = false;
                        this._forEachPage(function (curr) {
                            if (curr.elementUniqueID === uniqueID(element) || clearNext) {
                                curr.setElement(null, true);
                                clearNext = true;
                            }
                        });
                        this._setButtonStates();
                        return;
                    }

                    if (animateRemoval) {
                        var record = this._getAnimationRecord(element);
                        if (record) {
                            record.removed = true;
                        }
                    }
                    if (this._currentPage.elementUniqueID === uniqueID(element)) {
                        if (this._currentPage.next.elementUniqueID) {
                            this._shiftLeft(this._currentPage);
                            this._ensureCentered();
                        } else if (this._currentPage.prev.elementUniqueID) {
                            this._shiftRight(this._currentPage);
                        } else {
                            this._currentPage.setElement(null, true);
                        }
                    } else if (prevMarker.elementUniqueID === uniqueID(element)) {
                        if (prevMarker.next.element) {
                            work = this._itemsManager._previousItem(prevMarker.next.element).
                                then(function (e) {
                                    if (e === element) {
                                        // Because the VDS and Binding.List can send notifications in
                                        // different states we accomodate this here by fixing the case
                                        // where VDS hasn't yet removed an item when it sends a removed
                                        // or moved notification.
                                        //
                                        e = that._itemsManager._previousItem(e);
                                    }
                                    return e;
                                }).
                                then(function (e) {
                                    prevMarker.setElement(e, true);
                                });
                        } else {
                            prevMarker.setElement(null, true);
                        }
                    } else if (prevMarker.prev.elementUniqueID === uniqueID(element)) {
                        if (prevMarker.prev.prev && prevMarker.prev.prev.element) {
                            work = this._itemsManager._nextItem(prevMarker.prev.prev.element).
                                then(function (e) {
                                    if (e === element) {
                                        // Because the VDS and Binding.List can send notifications in
                                        // different states we accomodate this here by fixing the case
                                        // where VDS hasn't yet removed an item when it sends a removed
                                        // or moved notification.
                                        //
                                        e = that._itemsManager._nextItem(e);
                                    }
                                    return e;
                                }).
                                then(function (e) {
                                    prevMarker.prev.setElement(e, true);
                                });
                        } else {
                            prevMarker.prev.setElement(null, true);
                        }
                    } else {
                        var curr = this._currentPage.prev,
                            handled = false;
                        while (curr !== prevMarker && !handled) {
                            if (curr.elementUniqueID === uniqueID(element)) {
                                this._shiftRight(curr);
                                handled = true;
                            }

                            curr = curr.prev;
                        }

                        curr = this._currentPage.next;
                        while (curr !== prevMarker && !handled) {
                            if (curr.elementUniqueID === uniqueID(element)) {
                                this._shiftLeft(curr);
                                handled = true;
                            }

                            curr = curr.next;
                        }
                    }

                    return work.then(function () {
                        that._setButtonStates();
                    });
                },

                reload: function () {
                    this._writeProfilerMark("WinJS.UI.FlipView:reload,info");
                    this.resetState(0);
                },

                getItemSpacing: function () {
                    return this._itemSpacing;
                },

                setItemSpacing: function (space) {
                    this._itemSpacing = space;
                    this._ensureCentered();
                },

                notificationsStarted: function () {
                    this._writeProfilerMark("WinJS.UI.FlipView:changeNotifications,StartTM");
                    this._logBuffer();
                    this._notificationsStarted = this._notificationsStarted || 0;
                    this._notificationsStarted++;
                    // _notificationsEndedSignal is also used in the FlipView unit tests for coordination in the datasource tests
                    this._notificationsEndedSignal = new _Signal();
                    this._temporaryKeys = [];
                    this._animationRecords = {};
                    var that = this;
                    this._forEachPage(function (curr) {
                        that._createAnimationRecord(curr.elementUniqueID, curr);
                    });

                    // Since the current item is defined as the left-most item in the view, the only possible elements that can be in view at any time are
                    // the current item and the item proceeding it. We'll save these two elements for animations during the notificationsEnded cycle
                    this._animationRecords.currentPage = this._currentPage.element;
                    this._animationRecords.nextPage = this._currentPage.next.element;
                },

                notificationsEnded: function () {
                    // The animations are broken down into three parts.
                    // First, we move everything back to where it was before the changes happened. Elements that were inserted between two pages won't have their flip pages moved.
                    // Next, we figure out what happened to the two elements that used to be in view. If they were removed/moved, they get animated as appropriate in this order:
                    // removed, moved
                    // Finally, we figure out how the items that are now in view got there, and animate them as necessary, in this order: moved, inserted.
                    // The moved animation of the last part is joined with the moved animation of the previous part, so in the end it is:
                    // removed -> moved items in view + moved items not in view -> inserted.
                    var that = this;
                    this._endNotificationsWork && this._endNotificationsWork.cancel();
                    this._endNotificationsWork = this._ensureBufferConsistency().then(function () {
                        var animationPromises = [];
                        that._forEachPage(function (curr) {
                            var record = that._getAnimationRecord(curr.element);
                            if (record) {
                                if (record.changed) {
                                    record.oldElement.removedFromChange = true;
                                    animationPromises.push(that._changeFlipPage(curr, record.oldElement, record.newElement));
                                }
                                record.newLocation = curr.location;
                                that._setItemStart(curr, record.originalLocation);
                                if (record.inserted) {
                                    curr.elementRoot.style.opacity = 0.0;
                                }
                            }
                        });

                        function flipPageFromElement(element) {
                            var flipPage = null;
                            that._forEachPage(function (curr) {
                                if (curr.element === element) {
                                    flipPage = curr;
                                    return true;
                                }
                            });
                            return flipPage;
                        }

                        function animateOldViewportItemRemoved(record, item) {
                            that._writeProfilerMark("WinJS.UI.FlipView:_animateOldViewportItemRemoved,info");
                            var removedPage = that._createDiscardablePage(item);
                            that._setItemStart(removedPage, record.originalLocation);
                            animationPromises.push(that._deleteFlipPage(removedPage));
                        }

                        function animateOldViewportItemMoved(record, item) {
                            that._writeProfilerMark("WinJS.UI.FlipView:_animateOldViewportItemMoved,info");
                            var newLocation = record.originalLocation,
                                movedPage;
                            if (!record.successfullyMoved) {
                                // If the old visible item got moved, but the next/prev of that item don't match up with anything
                                // currently in our flip page buffer, we need to figure out in which direction it moved.
                                // The exact location doesn't matter since we'll be deleting it anyways, but we do need to
                                // animate it going in the right direction.
                                movedPage = that._createDiscardablePage(item);
                                var indexMovedTo = that._getElementIndex(item);
                                var newCurrentIndex = (that._currentPage.element ? that._getElementIndex(that._currentPage.element) : 0);
                                newLocation += (newCurrentIndex > indexMovedTo ? -100 * that._bufferSize : 100 * that._bufferSize);
                            } else {
                                movedPage = flipPageFromElement(item);
                                newLocation = record.newLocation;
                            }
                            if (movedPage) {
                                that._setItemStart(movedPage, record.originalLocation);
                                animationPromises.push(that._moveFlipPage(movedPage, function () {
                                    that._setItemStart(movedPage, newLocation);
                                }));
                            }
                        }

                        var oldCurrent = that._animationRecords.currentPage,
                            oldCurrentRecord = that._getAnimationRecord(oldCurrent),
                            oldNext = that._animationRecords.nextPage,
                            oldNextRecord = that._getAnimationRecord(oldNext);
                        if (oldCurrentRecord && oldCurrentRecord.changed) {
                            oldCurrent = oldCurrentRecord.newElement;
                        }
                        if (oldNextRecord && oldNextRecord.changed) {
                            oldNext = oldNextRecord.newElement;
                        }

                        if (oldCurrent !== that._currentPage.element || oldNext !== that._currentPage.next.element) {
                            if (oldCurrentRecord && oldCurrentRecord.removed) {
                                animateOldViewportItemRemoved(oldCurrentRecord, oldCurrent);
                            }
                            if (oldNextRecord && oldNextRecord.removed) {
                                animateOldViewportItemRemoved(oldNextRecord, oldNext);
                            }
                        }

                        function joinAnimationPromises() {
                            if (animationPromises.length === 0) {
                                animationPromises.push(Promise.wrap());
                            }

                            return Promise.join(animationPromises);
                        }
                        that._blockTabs = true;
                        joinAnimationPromises().then(function () {
                            animationPromises = [];
                            if (oldCurrentRecord && oldCurrentRecord.moved) {
                                animateOldViewportItemMoved(oldCurrentRecord, oldCurrent);
                            }
                            if (oldNextRecord && oldNextRecord.moved) {
                                animateOldViewportItemMoved(oldNextRecord, oldNext);
                            }
                            var newCurrRecord = that._getAnimationRecord(that._currentPage.element),
                                newNextRecord = that._getAnimationRecord(that._currentPage.next.element);
                            that._forEachPage(function (curr) {
                                var record = that._getAnimationRecord(curr.element);
                                if (record) {
                                    if (!record.inserted) {
                                        if (record.originalLocation !== record.newLocation) {
                                            if ((record !== oldCurrentRecord && record !== oldNextRecord) ||
                                                (record === oldCurrentRecord && !oldCurrentRecord.moved) ||
                                                (record === oldNextRecord && !oldNextRecord.moved)) {
                                                animationPromises.push(that._moveFlipPage(curr, function () {
                                                    that._setItemStart(curr, record.newLocation);
                                                }));
                                            }
                                        }
                                    } else if (record !== newCurrRecord && record !== newNextRecord) {
                                        curr.elementRoot.style.opacity = 1.0;
                                    }
                                }
                            });
                            joinAnimationPromises().then(function () {
                                animationPromises = [];
                                if (newCurrRecord && newCurrRecord.inserted) {
                                    animationPromises.push(that._insertFlipPage(that._currentPage));
                                }
                                if (newNextRecord && newNextRecord.inserted) {
                                    animationPromises.push(that._insertFlipPage(that._currentPage.next));
                                }
                                joinAnimationPromises().then(function () {
                                    that._checkElementVisibility(false);
                                    that._itemSettledOn();
                                    that._setListEnds();
                                    that._notificationsStarted--;
                                    if (that._notificationsStarted === 0) {
                                        that._notificationsEndedSignal.complete();
                                    }
                                    that._writeProfilerMark("WinJS.UI.FlipView:changeNotifications,StopTM");
                                    that._logBuffer();
                                    that._endNotificationsWork = null;
                                });
                            });
                        });
                    });
                },

                disableTouchFeatures: function () {
                    this._environmentSupportsTouch = false;
                    var panningContainerStyle = this._panningDivContainer.style;
                    this._panningDivContainer.removeEventListener(_BaseUtils._browserEventEquivalents["manipulationStateChanged"], this._handleManipulationStateChangedBound, true);
                    panningContainerStyle.overflowX = "hidden";
                    panningContainerStyle.overflowY = "hidden";
                    var panningContainerPropertiesToClear = [
                        "scroll-snap-type",
                        "scroll-snap-points-x",
                        "scroll-snap-points-y",
                        "scroll-limit-x-min",
                        "scroll-limit-x-max",
                        "scroll-limit-y-min",
                        "scroll-limit-y-max"
                    ];
                    panningContainerPropertiesToClear.forEach(function (propertyName) {
                        var platformPropertyName = styleEquivalents[propertyName];
                        if (platformPropertyName) {
                            panningContainerStyle[platformPropertyName.scriptName] = "";
                        }
                    });
                },

                // Private methods

                _hasFocus: {
                    get: function () {
                        return this._flipperDiv.contains(_Global.document.activeElement);
                    }
                },

                _timeoutPageSelection: function () {
                    var that = this;
                    if (this._lastTimeoutRequest) {
                        this._lastTimeoutRequest.cancel();
                    }
                    this._lastTimeoutRequest = Promise.timeout(itemSelectedEventDelay).then(function () {
                        that._itemSettledOn();
                    });
                },

                _updateTabIndex: function (newIndex) {
                    this._forEachPage(function (curr) {
                        if (curr.element) {
                            curr.element.tabIndex = newIndex;
                        }
                    });
                    this._tabIndex = newIndex;
                    this._tabManager.tabIndex = newIndex;
                },

                _releaseElementIfNotAnimated: function (element) {
                    var animatedRecord = this._getAnimationRecord(element);
                    if (!(animatedRecord && (animatedRecord.changed || animatedRecord.inserted || animatedRecord.moved || animatedRecord.removed))) {
                        this._itemsManager.releaseItem(element);
                    }
                },

                _getAnimationRecord: function (element) {
                    return (element ? this._animationRecords[uniqueID(element)] : null);
                },

                _createAnimationRecord: function (elementUniqueID, flipPage) {
                    if (elementUniqueID) {
                        var record = this._animationRecords[elementUniqueID] = {
                            removed: false,
                            changed: false,
                            inserted: false
                        };

                        if (flipPage) {
                            record.originalLocation = flipPage.location;
                        }

                        return record;
                    }
                },

                _writeProfilerMark: function (message) {
                    _WriteProfilerMark(message);
                    if (this._flipperDiv.winControl.constructor._enabledDebug) {
                        _Log.log && _Log.log(message, null, "flipviewdebug");
                    }
                },

                _getElementIndex: function (element) {
                    var index = 0;
                    try {
                        index = this._itemsManager.itemObject(element).index;
                    }
                    catch (e) {
                        // Failures are expected in cases where items are moved and then deleted. Animations will simply animate as if the item
                        // moved to the beginning of the list.
                    }
                    return index;
                },

                _resetBuffer: function (elementToSave, skipReleases) {
                    this._writeProfilerMark("WinJS.UI.FlipView:_resetBuffer,info");
                    var head = this._currentPage,
                        curr = head;
                    do {
                        if ((curr.element && curr.element === elementToSave) || skipReleases) {
                            curr.setElement(null, true);
                        } else {
                            curr.setElement(null);
                        }
                        curr = curr.next;
                    } while (curr !== head);
                },

                _getHeadOfBuffer: function () {
                    return this._prevMarker.prev;
                },

                _getTailOfBuffer: function () {
                    return this._prevMarker;
                },

                _insertNewFlipPage: function (prevElement) {
                    this._writeProfilerMark("WinJS.UI.FlipView:_insertNewFlipPage,info");
                    var newPage = this._createFlipPage(prevElement, this);
                    this._panningDiv.appendChild(newPage.pageRoot);
                    return newPage;
                },

                _fetchNextItems: function () {
                    this._writeProfilerMark("WinJS.UI.FlipView:_fetchNextItems,info");
                    var tail = Promise.wrap(this._currentPage);
                    var that = this;

                    for (var i = 0; i < this._bufferSize; i++) {
                        tail = tail.then(function (curr) {
                            if (curr.next === that._prevMarker) {
                                that._insertNewFlipPage(curr);
                            }
                            if (curr.element) {
                                return that._itemsManager._nextItem(curr.element).
                                    then(function (element) {
                                        curr.next.setElement(element);
                                        return curr.next;
                                    });
                            } else {
                                curr.next.setElement(null);
                                return curr.next;
                            }
                        });
                    }

                    return tail;
                },

                _fetchOneNext: function (target) {
                    this._writeProfilerMark("WinJS.UI.FlipView:_fetchOneNext,info");
                    var prevElement = target.prev.element;
                    // If the target we want to fill with the next item is the end of the circular buffer but we want to keep everything in memory, we've got to increase the buffer size
                    // so that we don't reuse prevMarker.
                    if (this._prevMarker === target) {
                        this._prevMarker = this._prevMarker.next;
                    }
                    if (!prevElement) {
                        target.setElement(null);
                        return;
                    }
                    var that = this;
                    return this._itemsManager._nextItem(prevElement).
                        then(function (element) {
                            target.setElement(element);
                            that._movePageAhead(target.prev, target);
                        });
                },

                _fetchPreviousItems: function (setPrevMarker) {
                    this._writeProfilerMark("WinJS.UI.FlipView:_fetchPreviousItems,info");
                    var that = this;

                    var tail = Promise.wrap(this._currentPage);

                    for (var i = 0; i < this._bufferSize; i++) {
                        tail = tail.then(function (curr) {
                            if (curr.element) {
                                return that._itemsManager._previousItem(curr.element).
                                    then(function (element) {
                                        curr.prev.setElement(element);
                                        return curr.prev;
                                    });
                            } else {
                                curr.prev.setElement(null);
                                return curr.prev;
                            }
                        });
                    }

                    return tail.then(function (curr) {
                        if (setPrevMarker) {
                            that._prevMarker = curr;
                        }
                    });
                },

                _fetchOnePrevious: function (target) {
                    this._writeProfilerMark("WinJS.UI.FlipView:_fetchOnePrevious,info");
                    var nextElement = target.next.element;

                    // If the target we want to fill with the previous item is the end of the circular buffer but we want to keep everything in memory, we've got to increase the buffer size
                    // so that we don't reuse prevMarker. We'll add a new element to be prevMarker's prev, then set prevMarker to point to that new element.
                    if (this._prevMarker === target.next) {
                        this._prevMarker = this._prevMarker.prev;
                    }
                    if (!nextElement) {
                        target.setElement(null);
                        return Promise.wrap();
                    }
                    var that = this;
                    return this._itemsManager._previousItem(nextElement).
                        then(function (element) {
                            target.setElement(element);
                            that._movePageBehind(target.next, target);
                        });
                },

                _setButtonStates: function () {
                    if (this._currentPage.prev.element) {
                        this._buttonVisibilityHandler.showPreviousButton();
                    } else {
                        this._buttonVisibilityHandler.hidePreviousButton();
                    }

                    if (this._currentPage.next.element) {
                        this._buttonVisibilityHandler.showNextButton();
                    } else {
                        this._buttonVisibilityHandler.hideNextButton();
                    }
                },

                _ensureCentered: function (delayBoundariesSet) {
                    this._writeProfilerMark("WinJS.UI.FlipView:_ensureCentered,info");
                    this._setItemStart(this._currentPage, leftBufferAmount * this._viewportSize());
                    var curr = this._currentPage;
                    while (curr !== this._prevMarker) {
                        this._movePageBehind(curr, curr.prev);
                        curr = curr.prev;
                    }

                    curr = this._currentPage;
                    while (curr.next !== this._prevMarker) {
                        this._movePageAhead(curr, curr.next);
                        curr = curr.next;
                    }
                    var boundariesSet = false;
                    if (this._lastScrollPos && !delayBoundariesSet) {
                        this._setListEnds();
                        boundariesSet = true;
                    }
                    this._lastScrollPos = this._getItemStart(this._currentPage);
                    this._setViewportStart(this._lastScrollPos);
                    this._checkElementVisibility(true);
                    this._setupSnapPoints();
                    if (!boundariesSet) {
                        this._setListEnds();
                    }
                },

                _ensureBufferConsistency: function () {
                    var that = this;
                    var currentElement = this._currentPage.element;
                    if (!currentElement) {
                        return Promise.wrap();
                    }

                    var refreshBuffer = false;
                    var seenUniqueIDs = {};
                    var seenLocations = {};
                    this._forEachPage(function (page) {
                        if (page && page.elementUniqueID) {
                            if (!seenUniqueIDs[page.elementUniqueID]) {
                                seenUniqueIDs[page.elementUniqueID] = true;
                            } else {
                                refreshBuffer = true;
                                return true;
                            }

                            if (page.location > 0) {
                                if (!seenLocations[page.location]) {
                                    seenLocations[page.location] = true;
                                } else {
                                    refreshBuffer = true;
                                    return true;
                                }
                            }
                        }
                    });

                    var animationKeys = Object.keys(this._animationRecords);
                    animationKeys.forEach(function (key) {
                        var record = that._animationRecords[key];
                        if (record && (record.changed || record.inserted || record.moved || record.removed)) {
                            refreshBuffer = true;
                        }
                    });

                    if (refreshBuffer) {
                        this._resetBuffer(null, true);
                        this._currentPage.setElement(currentElement);
                        return this._fetchNextItems().
                            then(function () {
                                return that._fetchPreviousItems(true);
                            }).
                            then(function () {
                                that._ensureCentered();
                            });
                    } else {
                        return Promise.wrap();
                    }
                },

                _shiftLeft: function (startingPoint) {
                    this._writeProfilerMark("WinJS.UI.FlipView:_shiftLeft,info");
                    var curr = startingPoint,
                        nextEl = null;
                    while (curr !== this._prevMarker && curr.next !== this._prevMarker) {
                        nextEl = curr.next.element;
                        if (!nextEl && curr.next.elementUniqueID) {
                            // Shift the uniqueID of the page manually since its element is being animated.
                            // This page  will not contain the element until the animation completes.
                            curr.elementUniqueID = curr.next.elementUniqueID;
                        }
                        curr.next.setElement(null, true);
                        curr.setElement(nextEl, true);
                        curr = curr.next;
                    }
                    if (curr !== this._prevMarker && curr.prev.element) {
                        var that = this;
                        return this._itemsManager._nextItem(curr.prev.element).
                            then(function (element) {
                                curr.setElement(element);
                                that._createAnimationRecord(curr.elementUniqueID, curr);
                            });
                    }
                },

                _logBuffer: function () {
                    if (this._flipperDiv.winControl.constructor._enabledDebug) {
                        _Log.log && _Log.log(this._currentPage.next.next.next.elementUniqueID + "\t@:" + this._currentPage.next.next.next.location + (this._currentPage.next.next.next.element ? ("\t" + this._currentPage.next.next.next.element.textContent) : ""), null, "flipviewdebug");
                        _Log.log && _Log.log(this._currentPage.next.next.next.next.elementUniqueID + "\t@:" + this._currentPage.next.next.next.next.location + (this._currentPage.next.next.next.next.element ? ("\t" + this._currentPage.next.next.next.next.element.textContent) : ""), null, "flipviewdebug");
                        _Log.log && _Log.log("> " + this._currentPage.elementUniqueID + "\t@:" + this._currentPage.location + (this._currentPage.element ? ("\t" + this._currentPage.element.textContent) : ""), null, "flipviewdebug");
                        _Log.log && _Log.log(this._currentPage.next.elementUniqueID + "\t@:" + this._currentPage.next.location + (this._currentPage.next.element ? ("\t" + this._currentPage.next.element.textContent) : ""), null, "flipviewdebug");
                        _Log.log && _Log.log(this._currentPage.next.next.elementUniqueID + "\t@:" + this._currentPage.next.next.location + (this._currentPage.next.next.element ? ("\t" + this._currentPage.next.next.element.textContent) : ""), null, "flipviewdebug");

                        var keys = Object.keys(this._itemsManager._elementMap);
                        var bufferKeys = [];
                        this._forEachPage(function (page) {
                            if (page && page.elementUniqueID) {
                                bufferKeys.push(page.elementUniqueID);
                            }
                        });
                        _Log.log && _Log.log("itemsmanager  = [" + keys.join(" ") + "] flipview [" + bufferKeys.join(" ") + "]", null, "flipviewdebug");
                    }
                },

                _shiftRight: function (startingPoint) {
                    this._writeProfilerMark("WinJS.UI.FlipView:_shiftRight,info");
                    var curr = startingPoint,
                        prevEl = null;
                    while (curr !== this._prevMarker) {
                        prevEl = curr.prev.element;
                        if (!prevEl && curr.prev.elementUniqueID) {
                            // Shift the uniqueID of the page manually since its element is being animated.
                            // This page  will not contain the element until the animation completes.
                            curr.elementUniqueID = curr.prev.elementUniqueID;
                        }
                        curr.prev.setElement(null, true);
                        curr.setElement(prevEl, true);
                        curr = curr.prev;
                    }
                    if (curr.next.element) {
                        var that = this;
                        return this._itemsManager._previousItem(curr.next.element).
                            then(function (element) {
                                curr.setElement(element);
                                that._createAnimationRecord(curr.elementUniqueID, curr);
                            });
                    }
                },

                _checkElementVisibility: function (viewWasReset) {
                    var i,
                        len;
                    if (viewWasReset) {
                        var currentElement = this._currentPage.element;
                        for (i = 0, len = this._visibleElements.length; i < len; i++) {
                            if (this._visibleElements[i] !== currentElement) {
                                this._announceElementInvisible(this._visibleElements[i]);
                            }
                        }

                        this._visibleElements = [];
                        if (currentElement) {
                            this._visibleElements.push(currentElement);
                            this._announceElementVisible(currentElement);
                        }
                    } else {
                        // Elements that have been removed completely from the flipper still need to raise pageVisibilityChangedEvents if they were visible prior to being removed,
                        // so before going through all the elements we go through the ones that we knew were visible and see if they're missing a parentNode. If they are,
                        // the elements were removed and we announce them as invisible.
                        for (i = 0, len = this._visibleElements.length; i < len; i++) {
                            if (!this._visibleElements[i].parentNode || this._visibleElements[i].removedFromChange) {
                                this._announceElementInvisible(this._visibleElements[i]);
                            }
                        }
                        this._visibleElements = [];
                        var that = this;
                        this._forEachPage(function (curr) {
                            var element = curr.element;
                            if (element) {
                                if (that._itemInView(curr)) {
                                    that._visibleElements.push(element);
                                    that._announceElementVisible(element);
                                } else {
                                    that._announceElementInvisible(element);
                                }
                            }
                        });
                    }
                },

                _announceElementVisible: function (element) {
                    if (element && !element.visible) {
                        element.visible = true;

                        var event = _Global.document.createEvent("CustomEvent");
                        this._writeProfilerMark("WinJS.UI.FlipView:pageVisibilityChangedEvent(visible:true),info");
                        event.initCustomEvent(_Constants.pageVisibilityChangedEvent, true, false, { source: this._flipperDiv, visible: true });

                        element.dispatchEvent(event);
                    }
                },

                _announceElementInvisible: function (element) {
                    if (element && element.visible) {
                        element.visible = false;

                        // Elements that have been removed from the flipper still need to fire invisible events, but they can't do that without being in the DOM.
                        // To fix that, we add the element back into the flipper, fire the event, then remove it.
                        var addedToDomForEvent = false;
                        if (!element.parentNode) {
                            addedToDomForEvent = true;
                            this._panningDivContainer.appendChild(element);
                        }

                        var event = _Global.document.createEvent("CustomEvent");
                        this._writeProfilerMark("WinJS.UI.FlipView:pageVisibilityChangedEvent(visible:false),info");
                        event.initCustomEvent(_Constants.pageVisibilityChangedEvent, true, false, { source: this._flipperDiv, visible: false });

                        element.dispatchEvent(event);
                        if (addedToDomForEvent) {
                            this._panningDivContainer.removeChild(element);
                        }
                    }
                },

                _createDiscardablePage: function (content) {
                    var pageDivs = this._createPageContainer(),
                        page = {
                            pageRoot: pageDivs.root,
                            elementRoot: pageDivs.elementContainer,
                            discardable: true,
                            element: content,
                            elementUniqueID: uniqueID(content),
                            discard: function () {
                                if (page.pageRoot.parentNode) {
                                    page.pageRoot.parentNode.removeChild(page.pageRoot);
                                }
                                if (page.element.parentNode) {
                                    page.element.parentNode.removeChild(page.element);
                                }
                            }
                        };
                    page.pageRoot.style.top = "0px";
                    page.elementRoot.appendChild(content);
                    this._panningDiv.appendChild(page.pageRoot);
                    return page;
                },

                _createPageContainer: function () {
                    var width = this._panningDivContainerOffsetWidth,
                        height = this._panningDivContainerOffsetHeight,
                        parentDiv = _Global.document.createElement("div"),
                        pageStyle = parentDiv.style,
                        flexBox = _Global.document.createElement("div");
                    flexBox.className = "win-item";
                    pageStyle.position = "absolute";
                    pageStyle.overflow = "hidden";
                    pageStyle.width = width + "px";
                    pageStyle.height = height + "px";

                    parentDiv.appendChild(flexBox);
                    return {
                        root: parentDiv,
                        elementContainer: flexBox
                    };
                },

                _createFlipPage: function (prev, manager) {
                    var page = {};
                    page.element = null;
                    page.elementUniqueID = null;

                    // The flip pages are managed as a circular doubly-linked list. this.currentItem should always refer to the current item in view, and this._prevMarker marks the point
                    // in the list where the last previous item is stored. Why a circular linked list?
                    // The virtualized flipper reuses its flip pages. When a new item is requested, the flipper needs to reuse an old item from the buffer. In the case of previous items,
                    // the flipper has to go all the way back to the farthest next item in the buffer and recycle it (which is why having a .prev pointer on the farthest previous item is really useful),
                    // and in the case of the next-most item, it needs to recycle next's next (ie, the this._prevMarker). The linked structure comes in really handy when iterating through the list
                    // and separating out prev items from next items (like removed and ensureCentered do). If we were to use a structure like an array it would be pretty messy to do that and still
                    // maintain a buffer of recyclable items.
                    if (!prev) {
                        page.next = page;
                        page.prev = page;
                    } else {
                        page.prev = prev;
                        page.next = prev.next;
                        page.next.prev = page;
                        prev.next = page;
                    }
                    var pageContainer = this._createPageContainer();
                    page.elementRoot = pageContainer.elementContainer;
                    page.elementRoot.style["msOverflowStyle"] = "auto";
                    page.pageRoot = pageContainer.root;

                    // Sets the element to display in this flip page
                    page.setElement = function (element, isReplacement) {
                        if (element === undefined) {
                            element = null;
                        }
                        if (element === page.element) {
                            if (!element) {
                                // If there are data source updates during the animation (e.g. item removed), a page element can be set to null when the shiftLeft/Right functions
                                // call this function with a null element. However, since the element in the page is in the middle of an animation its page.elementUniqueID
                                // is still set, so we need to explicitly clear its value so that when the animation completes, the animated element is not
                                // restored back into the internal buffer.
                                page.elementUniqueID = null;
                            }
                            return;
                        }
                        if (page.element) {
                            if (!isReplacement) {
                                manager._itemsManager.releaseItem(page.element);
                                _Dispose._disposeElement(page.element);
                            }
                        }
                        page.element = element;
                        page.elementUniqueID = (element ? uniqueID(element) : null);
                        _ElementUtilities.empty(page.elementRoot);

                        if (page.element) {
                            if (page === manager._currentPage) {
                                manager._tabManager.childFocus = element;
                            }
                            if (!isFlipper(page.element)) {
                                page.element.tabIndex = manager._tabIndex;
                                page.element.setAttribute("role", "option");
                                page.element.setAttribute("aria-selected", false);
                                if (!page.element.id) {
                                    page.element.id = uniqueID(page.element);
                                }

                                var setFlowAttribute = function (source, target, attributeName) {
                                    source.setAttribute(attributeName, target.id);
                                };

                                var isEnd = !page.next.element || page === manager._prevMarker.prev;
                                if (isEnd) {
                                    setFlowAttribute(page.element, manager._bufferAriaEndMarker, "aria-flowto");
                                    setFlowAttribute(manager._bufferAriaEndMarker, page.element, "x-ms-aria-flowfrom");
                                }

                                if (page !== manager._prevMarker && page.prev.element) {
                                    setFlowAttribute(page.prev.element, page.element, "aria-flowto");
                                    setFlowAttribute(page.element, page.prev.element, "x-ms-aria-flowfrom");
                                }
                                if (page.next !== manager._prevMarker && page.next.element) {
                                    setFlowAttribute(page.element, page.next.element, "aria-flowto");
                                    setFlowAttribute(page.next.element, page.element, "x-ms-aria-flowfrom");
                                }

                                if (!page.prev.element) {
                                    setFlowAttribute(page.element, manager._bufferAriaStartMarker, "x-ms-aria-flowfrom");
                                    // aria-flowto in the start marker is configured in itemSettledOn to point to the current page in view
                                }
                            }
                            page.elementRoot.appendChild(page.element);
                        }
                    };

                    return page;
                },

                _itemInView: function (flipPage) {
                    return this._itemEnd(flipPage) > this._getViewportStart() && this._getItemStart(flipPage) < this._viewportEnd();
                },

                _getViewportStart: function () {
                    if (!this._panningDivContainer.parentNode) {
                        return;
                    }
                    if (this._isHorizontal) {
                        return _ElementUtilities.getScrollPosition(this._panningDivContainer).scrollLeft;
                    } else {
                        return _ElementUtilities.getScrollPosition(this._panningDivContainer).scrollTop;
                    }
                },

                _setViewportStart: function (newValue) {
                    if (!this._panningDivContainer.parentNode) {
                        return;
                    }
                    if (this._isHorizontal) {
                        _ElementUtilities.setScrollPosition(this._panningDivContainer, { scrollLeft: newValue });
                    } else {
                        _ElementUtilities.setScrollPosition(this._panningDivContainer, { scrollTop: newValue });
                    }
                },

                _viewportEnd: function () {
                    var element = this._panningDivContainer;
                    if (this._isHorizontal) {
                        if (this._rtl) {
                            return this._getViewportStart() + this._panningDivContainerOffsetWidth;
                        } else {
                            return _ElementUtilities.getScrollPosition(element).scrollLeft + this._panningDivContainerOffsetWidth;
                        }
                    } else {
                        return element.scrollTop + this._panningDivContainerOffsetHeight;
                    }
                },

                _viewportSize: function () {
                    return this._isHorizontal ? this._panningDivContainerOffsetWidth : this._panningDivContainerOffsetHeight;
                },

                _getItemStart: function (flipPage) {
                    return flipPage.location;
                },

                _setItemStart: function (flipPage, newValue) {

                    if (this._isHorizontal) {
                        flipPage.pageRoot.style.left = (this._rtl ? -newValue : newValue) + "px";
                    } else {
                        flipPage.pageRoot.style.top = newValue + "px";
                    }
                    flipPage.location = newValue;
                },

                _itemEnd: function (flipPage) {
                    return (this._isHorizontal ? flipPage.location + this._panningDivContainerOffsetWidth : flipPage.location + this._panningDivContainerOffsetHeight) + this._itemSpacing;
                },

                _itemSize: function () {
                    return this._isHorizontal ? this._panningDivContainerOffsetWidth : this._panningDivContainerOffsetHeight;
                },

                _movePageAhead: function (referencePage, pageToPlace) {
                    var delta = this._itemSize(referencePage) + this._itemSpacing;
                    this._setItemStart(pageToPlace, this._getItemStart(referencePage) + delta);
                },

                _movePageBehind: function (referencePage, pageToPlace) {
                    var delta = this._itemSize(referencePage) + this._itemSpacing;
                    this._setItemStart(pageToPlace, this._getItemStart(referencePage) - delta);
                },

                _setupSnapPoints: function () {
                    if (!this._environmentSupportsTouch) {
                        return;
                    }
                    var containerStyle = this._panningDivContainer.style;
                    containerStyle[styleEquivalents["scroll-snap-type"].scriptName] = "mandatory";
                    var viewportSize = this._viewportSize();
                    var snapInterval = viewportSize + this._itemSpacing;
                    var propertyName = "scroll-snap-points";
                    var startSnap = 0;
                    var currPos = this._getItemStart(this._currentPage);
                    startSnap = currPos % (viewportSize + this._itemSpacing);
                    containerStyle[styleEquivalents[(this._isHorizontal ? propertyName + "-x" : propertyName + "-y")].scriptName] = "snapInterval(" + startSnap + "px, " + snapInterval + "px)";
                },

                _setListEnds: function () {
                    if (!this._environmentSupportsTouch) {
                        return;
                    }

                    if (this._currentPage.element) {
                        var containerStyle = this._panningDivContainer.style,
                            startScroll = 0,
                            endScroll = 0,
                            startNonEmptyPage = this._getTailOfBuffer(),
                            endNonEmptyPage = this._getHeadOfBuffer(),
                            startBoundaryStyle = styleEquivalents["scroll-limit-" + (this._isHorizontal ? "x-min" : "y-min")].scriptName,
                            endBoundaryStyle = styleEquivalents["scroll-limit-" + (this._isHorizontal ? "x-max" : "y-max")].scriptName;

                        while (!endNonEmptyPage.element) {
                            endNonEmptyPage = endNonEmptyPage.prev;

                            // We started at the item before prevMarker (going backwards), so we will exit if all
                            // the pages in the buffer are empty.
                            if (endNonEmptyPage === this._prevMarker.prev) {
                                break;
                            }
                        }

                        while (!startNonEmptyPage.element) {
                            startNonEmptyPage = startNonEmptyPage.next;

                            // We started at prevMarker (going forward), so we will exit if all the pages in the
                            // buffer are empty.
                            if (startNonEmptyPage === this._prevMarker) {
                                break;
                            }
                        }

                        endScroll = this._getItemStart(endNonEmptyPage);
                        startScroll = this._getItemStart(startNonEmptyPage);
                        containerStyle[startBoundaryStyle] = startScroll + "px";
                        containerStyle[endBoundaryStyle] = endScroll + "px";
                    }
                },

                _viewportOnItemStart: function () {
                    return this._getItemStart(this._currentPage) === this._getViewportStart();
                },

                _restoreAnimatedElement: function (oldPage, discardablePage) {
                    var removed = true;
                    // Restore the element in the old page only if it still matches the uniqueID, and the page
                    // does not have new updated content. If the element was removed, it won't be restore in the
                    // old page.
                    if (oldPage.elementUniqueID === uniqueID(discardablePage.element) && !oldPage.element) {
                        oldPage.setElement(discardablePage.element, true);
                        removed = false;
                    } else {
                        // Iterate through the pages to see if the element was moved
                        this._forEachPage(function (curr) {
                            if (curr.elementUniqueID === discardablePage.elementUniqueID && !curr.element) {
                                curr.setElement(discardablePage.element, true);
                                removed = false;
                            }
                        });
                    }
                    return removed;
                },

                _itemSettledOn: function () {
                    if (this._lastTimeoutRequest) {
                        this._lastTimeoutRequest.cancel();
                        this._lastTimeoutRequest = null;
                    }

                    var that = this;
                    // Need to yield to the host here
                    _BaseUtils._setImmediate(function () {
                        if (that._viewportOnItemStart()) {
                            that._blockTabs = false;
                            if (that._currentPage.element) {
                                if (that._lastSelectedElement !== that._currentPage.element) {
                                    if (that._lastSelectedPage && that._lastSelectedPage.element && !isFlipper(that._lastSelectedPage.element)) {
                                        that._lastSelectedPage.element.setAttribute("aria-selected", false);
                                    }
                                    that._lastSelectedPage = that._currentPage;
                                    that._lastSelectedElement = that._currentPage.element;
                                    if (!isFlipper(that._currentPage.element)) {
                                        that._currentPage.element.setAttribute("aria-selected", true);
                                    }

                                    // Need to schedule this:
                                    // - to be able to register for the pageselected event after instantiating the control and still get the event
                                    // - in case a FlipView navigation is triggered inside the pageselected listener (avoid reentering _itemSettledOn)
                                    Scheduler.schedule(function FlipView_dispatchPageSelectedEvent() {
                                        if (that._currentPage.element) {
                                            if (that._hasFocus || that._hadFocus) {
                                                that._hadFocus = false;
                                                _ElementUtilities._setActive(that._currentPage.element);
                                                that._tabManager.childFocus = that._currentPage.element;
                                            }
                                            var event = _Global.document.createEvent("CustomEvent");
                                            event.initCustomEvent(_Constants.pageSelectedEvent, true, false, { source: that._flipperDiv });
                                            that._writeProfilerMark("WinJS.UI.FlipView:pageSelectedEvent,info");
                                            that._currentPage.element.dispatchEvent(event);

                                            // Fire the pagecompleted event when the render completes if we are still looking  at the same element.
                                            // Check that the current element is not null, since the app could've triggered a navigation inside the
                                            // pageselected event handler.
                                            var originalElement = that._currentPage.element;
                                            if (originalElement) {
                                                var record = that._itemsManager._recordFromElement(originalElement, true);
                                                if (record) {
                                                    record.renderComplete.then(function () {
                                                        if (originalElement === that._currentPage.element) {
                                                            that._currentPage.element.setAttribute("aria-setsize", that._cachedSize);
                                                            that._currentPage.element.setAttribute("aria-posinset", that.currentIndex() + 1);
                                                            that._bufferAriaStartMarker.setAttribute("aria-flowto", that._currentPage.element.id);
                                                            event = _Global.document.createEvent("CustomEvent");
                                                            event.initCustomEvent(_Constants.pageCompletedEvent, true, false, { source: that._flipperDiv });
                                                            that._writeProfilerMark("WinJS.UI.FlipView:pageCompletedEvent,info");
                                                            that._currentPage.element.dispatchEvent(event);
                                                        }
                                                    });
                                                }
                                            }
                                        }
                                    }, Scheduler.Priority.normal, null, "WinJS.UI.FlipView._dispatchPageSelectedEvent");
                                }
                            }
                        }
                    });
                },

                _forEachPage: function (callback) {
                    var curr = this._prevMarker;
                    do {
                        if (callback(curr)) {
                            break;
                        }
                        curr = curr.next;
                    } while (curr !== this._prevMarker);
                },

                _changeFlipPage: function (page, oldElement, newElement) {
                    this._writeProfilerMark("WinJS.UI.FlipView:_changeFlipPage,info");
                    page.element = null;
                    if (page.setElement) {
                        page.setElement(newElement, true);
                    } else {
                        // Discardable pages that are created for animations aren't full fleged pages, and won't have some of the functions a normal page would.
                        // changeFlipPage will be called on them when an item that's animating gets fetched. When that happens, we need to replace its element
                        // manually, then center it.
                        oldElement.parentNode.removeChild(oldElement);
                        page.elementRoot.appendChild(newElement);
                    }

                    var style = oldElement.style;
                    style.position = "absolute";
                    style.left = "0px";
                    style.top = "0px";
                    style.opacity = 1.0;

                    page.pageRoot.appendChild(oldElement);
                    oldElement.style.left = Math.max(0, (page.pageRoot.offsetWidth - oldElement.offsetWidth) / 2) + "px";
                    oldElement.style.top = Math.max(0, (page.pageRoot.offsetHeight - oldElement.offsetHeight) / 2) + "px";

                    return Animations.fadeOut(oldElement).then(function () {
                        oldElement.parentNode && oldElement.parentNode.removeChild(oldElement);
                    });
                },

                _deleteFlipPage: function (page) {
                    _WriteProfilerMark("WinJS.UI.FlipView:_deleteFlipPage,info");
                    page.elementRoot.style.opacity = 0;
                    var animation = Animations.createDeleteFromListAnimation([page.elementRoot]);

                    var that = this;
                    return animation.execute().then(function () {
                        if (page.discardable) {
                            page.discard();
                            that._itemsManager.releaseItem(page.element);
                        }
                    });
                },

                _insertFlipPage: function (page) {
                    _WriteProfilerMark("WinJS.UI.FlipView:_insertFlipPage,info");
                    page.elementRoot.style.opacity = 1.0;
                    var animation = Animations.createAddToListAnimation([page.elementRoot]);

                    return animation.execute().then(function () {
                        if (page.discardable) {
                            page.discard();
                        }
                    });
                },

                _moveFlipPage: function (page, move) {
                    _WriteProfilerMark("WinJS.UI.FlipView:_moveFlipPage,info");
                    var animation = Animations.createRepositionAnimation(page.pageRoot);

                    move();

                    var that = this;
                    return animation.execute().then(function () {
                        if (page.discardable) {
                            page.discard();
                            var animationRecord = that._getAnimationRecord(page.element);
                            if (animationRecord && !animationRecord.successfullyMoved) {
                                // If the animationRecord was not succesfully moved, the item is now outside of the buffer,
                                // and we can release it.
                                that._itemsManager.releaseItem(page.element);
                            }
                        }
                    });
                },

                _handleManipulationStateChanged: function (event) {
                    this._manipulationState = event.currentState;
                    if (event.currentState === 0 && event.target === this._panningDivContainer) {
                        this._itemSettledOn();
                        this._ensureCentered();
                    }
                }
            }, {
                supportedForProcessing: false,
            });
            _FlipPageManager.flipPageBufferCount = 2; // The number of items that should surround the current item as a buffer at any time
            return _FlipPageManager;
        })
    });

});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/FlipView',[
    '../Core/_Global',
    '../Core/_Base',
    '../Core/_BaseUtils',
    '../Core/_ErrorFromName',
    '../Core/_Events',
    '../Core/_Resources',
    '../Core/_WriteProfilerMark',
    '../Animations',
    '../Animations/_TransitionAnimation',
    '../BindingList',
    '../Promise',
    '../Scheduler',
    '../Utilities/_Control',
    '../Utilities/_Dispose',
    '../Utilities/_ElementUtilities',
    '../Utilities/_Hoverable',
    '../Utilities/_ItemsManager',
    '../Utilities/_UI',
    './FlipView/_Constants',
    './FlipView/_PageManager',
    'require-style!less/desktop/controls',
    'require-style!less/phone/controls'
], function flipperInit(_Global, _Base, _BaseUtils, _ErrorFromName, _Events, _Resources, _WriteProfilerMark, Animations, _TransitionAnimation, BindingList, Promise, Scheduler, _Control, _Dispose, _ElementUtilities, _Hoverable, _ItemsManager, _UI, _Constants, _PageManager) {
    "use strict";

    _Base.Namespace.define("WinJS.UI", {
        /// <field>
        /// <summary locid="WinJS.UI.FlipView">
        /// Displays a collection, such as a set of photos, one item at a time.
        /// </summary>
        /// </field>
        /// <icon src="ui_winjs.ui.flipview.12x12.png" width="12" height="12" />
        /// <icon src="ui_winjs.ui.flipview.16x16.png" width="16" height="16" />
        /// <htmlSnippet><![CDATA[<div data-win-control="WinJS.UI.FlipView"></div>]]></htmlSnippet>
        /// <event name="datasourcecountchanged" bubbles="true" locid="WinJS.UI.FlipView_e:datasourcecountchanged">Raised when the number of items in the itemDataSource changes.</event>
        /// <event name="pagevisibilitychanged" bubbles="true" locid="WinJS.UI.FlipView_e:pagevisibilitychanged">Raised when a FlipView page becomes visible or invisible.</event>
        /// <event name="pageselected" bubbles="true" locid="WinJS.UI.FlipView_e:pageselected">Raised when the FlipView flips to a page.</event>
        /// <event name="pagecompleted" bubbles="true" locid="WinJS.UI.FlipView_e:pagecompleted">Raised when the FlipView flips to a page and its renderer function completes.</event>
        /// <part name="flipView" class="win-flipview" locid="WinJS.UI.FlipView_part:flipView">The entire FlipView control.</part>
        /// <part name="navigationButton" class="win-navbutton" locid="WinJS.UI.FlipView_part:navigationButton">The general class for all FlipView navigation buttons.</part>
        /// <part name="leftNavigationButton" class="win-navleft" locid="WinJS.UI.FlipView_part:leftNavigationButton">The left navigation button.</part>
        /// <part name="rightNavigationButton" class="win-navright" locid="WinJS.UI.FlipView_part:rightNavigationButton">The right navigation button.</part>
        /// <part name="topNavigationButton" class="win-navtop" locid="WinJS.UI.FlipView_part:topNavigationButton">The top navigation button.</part>
        /// <part name="bottomNavigationButton" class="win-navbottom" locid="WinJS.UI.FlipView_part:bottomNavigationButton">The bottom navigation button.</part>
        /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
        /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
        /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
        FlipView: _Base.Namespace._lazy(function () {

            // Class names
            var navButtonClass = "win-navbutton",
                flipViewClass = "win-flipview",
                navButtonLeftClass = "win-navleft",
                navButtonRightClass = "win-navright",
                navButtonTopClass = "win-navtop",
                navButtonBottomClass = "win-navbottom";

            // Aria labels
            var previousButtonLabel = "Previous",
                nextButtonLabel = "Next";

            var buttonFadeDelay = 3000,
                avoidTrapDelay = 500,
                leftArrowGlyph = "&#57570;",
                rightArrowGlyph = "&#57571;",
                topArrowGlyph = "&#57572;",
                bottomArrowGlyph = "&#57573;",
                animationMoveDelta = 40;

            function flipViewPropertyChanged(list) {
                var that = list[0].target.winControl;
                if (that && that instanceof FlipView) {
                    if (list.some(function (record) {
                        if (record.attributeName === "dir") {
                            return true;
                    } else if (record.attributeName === "style") {
                            return (that._cachedStyleDir !== record.target.style.direction);
                    } else {
                            return false;
                    }
                    })) {
                        that._cachedStyleDir = that._flipviewDiv.style.direction;
                        that._rtl = _Global.getComputedStyle(that._flipviewDiv, null).direction === "rtl";
                        that._setupOrientation();
                    }
                }
            }

            function flipviewResized(e) {
                var that = e.target && e.target.winControl;
                if (that && that instanceof FlipView) {
                    _WriteProfilerMark("WinJS.UI.FlipView:resize,StartTM");
                    that._resize();
                }
            }

            var strings = {
                get badAxis() { return "Invalid argument: orientation must be a string, either 'horizontal' or 'vertical'"; },
                get badCurrentPage() { return "Invalid argument: currentPage must be a number greater than or equal to zero and be within the bounds of the datasource"; },
                get noitemsManagerForCount() { return "Invalid operation: can't get count if no dataSource has been set"; },
                get badItemSpacingAmount() { return "Invalid argument: itemSpacing must be a number greater than or equal to zero"; },
                get navigationDuringStateChange() { return "Error: After changing itemDataSource or itemTemplate, any navigation in the FlipView control should be delayed until the pageselected event is fired."; },
                get panningContainerAriaLabel() { return _Resources._getWinJSString("ui/flipViewPanningContainerAriaLabel").value; }
            };

            var FlipView = _Base.Class.define(function FlipView_ctor(element, options) {
                /// <signature helpKeyword="WinJS.UI.FlipView.FlipView">
                /// <summary locid="WinJS.UI.FlipView.constructor">
                /// Creates a new FlipView.
                /// </summary>
                /// <param name="element" domElement="true" locid="WinJS.UI.FlipView.constructor_p:element">
                /// The DOM element that hosts the control.
                /// </param>
                /// <param name="options" type="Object" locid="WinJS.UI.FlipView.constructor_p:options">
                /// An object that contains one or more property/value pairs to apply to the new control.
                /// Each property of the options object corresponds to one of the control's properties or events.
                /// Event names must begin with "on". For example, to provide a handler for the pageselected event,
                /// add a property named "onpageselected" to the options object and set its value to the event handler.
                /// This parameter is optional.
                /// </param>
                /// <returns type="WinJS.UI.FlipView" locid="WinJS.UI.FlipView.constructor_returnValue">
                /// The new FlipView control.
                /// </returns>
                /// </signature>
                _WriteProfilerMark("WinJS.UI.FlipView:constructor,StartTM");

                this._disposed = false;

                element = element || _Global.document.createElement("div");

                var isHorizontal = true,
                    dataSource = null,
                    itemRenderer = _ItemsManager._trivialHtmlRenderer,
                    initialIndex = 0,
                    itemSpacing = 0;

                if (options) {
                    // flipAxis parameter checking. Must be a string, either "horizontal" or "vertical"
                    if (options.orientation) {
                        if (typeof options.orientation === "string") {
                            switch (options.orientation.toLowerCase()) {
                                case "horizontal":
                                    isHorizontal = true;
                                    break;

                                case "vertical":
                                    isHorizontal = false;
                                    break;
                            }
                        }
                    }

                    if (options.currentPage) {
                        initialIndex = options.currentPage >> 0;
                        initialIndex = initialIndex < 0 ? 0 : initialIndex;
                    }

                    if (options.itemDataSource) {
                        dataSource = options.itemDataSource;
                    }

                    if (options.itemTemplate) {
                        itemRenderer = this._getItemRenderer(options.itemTemplate);
                    }

                    if (options.itemSpacing) {
                        itemSpacing = options.itemSpacing >> 0;
                        itemSpacing = itemSpacing < 0 ? 0 : itemSpacing;
                    }
                }

                if (!dataSource) {
                    var list = new BindingList.List();
                    dataSource = list.dataSource;
                }
                _ElementUtilities.empty(element);

                // Set _flipviewDiv so the element getter works correctly, then call _setOption with eventsOnly flag on before calling _initializeFlipView
                // so that event listeners are added before page loading
                this._flipviewDiv = element;
                element.winControl = this;
                _Control._setOptions(this, options, true);
                this._initializeFlipView(element, isHorizontal, dataSource, itemRenderer, initialIndex, itemSpacing);
                _ElementUtilities.addClass(element, "win-disposable");
                this._avoidTrappingTime = 0;
                this._windowWheelHandlerBound = this._windowWheelHandler.bind(this);
                _ElementUtilities._globalListener.addEventListener(element, 'wheel', this._windowWheelHandlerBound);
                _ElementUtilities._globalListener.addEventListener(element, 'mousewheel', this._windowWheelHandlerBound);

                _WriteProfilerMark("WinJS.UI.FlipView:constructor,StopTM");
            }, {

                // Public methods

                dispose: function FlipView_dispose() {
                    /// <signature helpKeyword="WinJS.UI.FlipView.dispose">
                    /// <summary locid="WinJS.UI.FlipView.dispose">
                    /// Disposes this FlipView.
                    /// </summary>
                    /// </signature>
                    _WriteProfilerMark("WinJS.UI.FlipView:dispose,StopTM");
                    if (this._disposed) {
                        return;
                    }

                    _ElementUtilities._globalListener.removeEventListener(this._flipviewDiv, 'wheel', this._windowWheelHandlerBound);
                    _ElementUtilities._globalListener.removeEventListener(this._flipviewDiv, 'mousewheel', this._windowWheelHandlerBound);
                    _ElementUtilities._resizeNotifier.unsubscribe(this._flipviewDiv, flipviewResized);


                    this._disposed = true;
                    this._pageManager.dispose();
                    this._itemsManager.release();
                    this.itemDataSource = null;
                },

                next: function FlipView_next() {
                    /// <signature helpKeyword="WinJS.UI.FlipView.next">
                    /// <summary locid="WinJS.UI.FlipView.next">
                    /// Navigates to the next item.
                    /// </summary>
                    /// <returns type="Boolean" locid="WinJS.UI.FlipView.next_returnValue">
                    /// true if the FlipView begins navigating to the next page;
                    /// false if the FlipView is at the last page or is in the middle of another navigation animation.
                    /// </returns>
                    /// </signature>
                    _WriteProfilerMark("WinJS.UI.FlipView:next,info");
                    var cancelAnimationCallback = this._nextAnimation ? null : this._cancelDefaultAnimation;
                    return this._navigate(true, cancelAnimationCallback);
                },

                previous: function FlipView_previous() {
                    /// <signature helpKeyword="WinJS.UI.FlipView.previous">
                    /// <summary locid="WinJS.UI.FlipView.previous">
                    /// Navigates to the previous item.
                    /// </summary>
                    /// <returns type="Boolean" locid="WinJS.UI.FlipView.previous_returnValue">
                    /// true if FlipView begins navigating to the previous page;
                    /// false if the FlipView is already at the first page or is in the middle of another navigation animation.
                    /// </returns>
                    /// </signature>
                    _WriteProfilerMark("WinJS.UI.FlipView:prev,info");
                    var cancelAnimationCallback = this._prevAnimation ? null : this._cancelDefaultAnimation;
                    return this._navigate(false, cancelAnimationCallback);
                },

                /// <field type="HTMLElement" domElement="true" hidden="true" locid="WinJS.UI.FlipView.element" helpKeyword="WinJS.UI.FlipView.element">
                /// The DOM element that hosts the FlipView control.
                /// </field>
                element: {
                    get: function () {
                        return this._flipviewDiv;
                    }
                },

                /// <field type="Number" integer="true" locid="WinJS.UI.FlipView.currentPage" helpKeyword="WinJS.UI.FlipView.currentPage" minimum="0">
                /// Gets or sets the index of the currently displayed page. The minimum value is 0 and the maximum value is one less than the total number of items returned by the data source.
                /// </field>
                currentPage: {
                    get: function () {
                        return this._getCurrentIndex();
                    },
                    set: function (index) {
                        _WriteProfilerMark("WinJS.UI.FlipView:set_currentPage,info");

                        if (this._pageManager._notificationsEndedSignal) {
                            var that = this;
                            this._pageManager._notificationsEndedSignal.promise.done(function () {
                                that._pageManager._notificationsEndedSignal = null;
                                that.currentPage = index;
                            });
                            return;
                        }

                        if (this._animating && !this._cancelAnimation()) {
                            return;
                        }

                        index = index >> 0;
                        index = index < 0 ? 0 : index;

                        if (this._refreshTimer) {
                            this._indexAfterRefresh = index;
                        } else {
                            if (this._pageManager._cachedSize > 0) {
                                index = Math.min(this._pageManager._cachedSize - 1, index);
                            } else if (this._pageManager._cachedSize === 0) {
                                index = 0;
                            }

                            var that = this;
                            if (this._jumpingToIndex === index) {
                                return;
                            }
                            var clearJumpToIndex = function () {
                                that._jumpingToIndex = null;
                            };
                            this._jumpingToIndex = index;
                            var jumpAnimation = (this._jumpAnimation ? this._jumpAnimation : this._defaultAnimation.bind(this)),
                                cancelAnimationCallback = (this._jumpAnimation ? null : this._cancelDefaultAnimation),
                                completionCallback = function () { that._completeJump(); };
                            this._pageManager.startAnimatedJump(index, cancelAnimationCallback, completionCallback).
                            then(function (elements) {
                                if (elements) {
                                    that._animationsStarted();
                                    var currElement = elements.oldPage.pageRoot;
                                    var newCurrElement = elements.newPage.pageRoot;
                                    that._contentDiv.appendChild(currElement);
                                    that._contentDiv.appendChild(newCurrElement);

                                    that._completeJumpPending = true;
                                    jumpAnimation(currElement, newCurrElement).
                                        then(function () {
                                            if (that._completeJumpPending) {
                                                completionCallback();
                                                _WriteProfilerMark("WinJS.UI.FlipView:set_currentPage.animationComplete,info");
                                            }
                                        }).done(clearJumpToIndex, clearJumpToIndex);
                                } else {
                                    clearJumpToIndex();
                                }
                            }, clearJumpToIndex);
                        }
                    }
                },

                /// <field type="String" oamOptionsDatatype="WinJS.UI.Orientation" locid="WinJS.UI.FlipView.orientation" helpKeyword="WinJS.UI.FlipView.orientation">
                /// Gets or sets the layout orientation of the FlipView, horizontal or vertical.
                /// </field>
                orientation: {
                    get: function () {
                        return this._axisAsString();
                    },
                    set: function (orientation) {
                        _WriteProfilerMark("WinJS.UI.FlipView:set_orientation,info");
                        var isHorizontal = orientation === "horizontal";
                        if (isHorizontal !== this._isHorizontal) {
                            this._isHorizontal = isHorizontal;
                            this._setupOrientation();
                            this._pageManager.setOrientation(this._isHorizontal);
                        }
                    }
                },

                /// <field type="object" locid="WinJS.UI.FlipView.itemDataSource" helpKeyword="WinJS.UI.FlipView.itemDataSource">
                /// Gets or sets the data source that provides the FlipView with items to display.
                /// The FlipView displays one item at a time, each on its own page.
                /// </field>
                itemDataSource: {
                    get: function () {
                        return this._dataSource;
                    },

                    set: function (dataSource) {
                        _WriteProfilerMark("WinJS.UI.FlipView:set_itemDataSource,info");
                        this._dataSourceAfterRefresh = dataSource || new BindingList.List().dataSource;
                        this._refresh();
                    }
                },

                /// <field type="Function" locid="WinJS.UI.FlipView.itemTemplate" helpKeyword="WinJS.UI.FlipView.itemTemplate" potentialValueSelector="[data-win-control='WinJS.Binding.Template']">
                /// Gets or sets a WinJS.Binding.Template or a function that defines the HTML for each item's page.
                /// </field>
                itemTemplate: {
                    get: function () {
                        return this._itemRenderer;
                    },

                    set: function (itemTemplate) {
                        _WriteProfilerMark("WinJS.UI.FlipView:set_itemTemplate,info");
                        this._itemRendererAfterRefresh = this._getItemRenderer(itemTemplate);
                        this._refresh();
                    }
                },

                /// <field type="Number" integer="true" locid="WinJS.UI.FlipView.itemSpacing" helpKeyword="WinJS.UI.FlipView.itemSpacing">
                /// Gets or sets the spacing between items, in pixels.
                /// </field>
                itemSpacing: {
                    get: function () {
                        return this._pageManager.getItemSpacing();
                    },

                    set: function (spacing) {
                        _WriteProfilerMark("WinJS.UI.FlipView:set_itemSpacing,info");
                        spacing = spacing >> 0;
                        spacing = spacing < 0 ? 0 : spacing;
                        this._pageManager.setItemSpacing(spacing);
                    }
                },

                count: function FlipView_count() {
                    /// <signature helpKeyword="WinJS.UI.FlipView.count">
                    /// <summary locid="WinJS.UI.FlipView.count">
                    /// Returns the number of items in the FlipView object's itemDataSource.
                    /// </summary>
                    /// <returns type="WinJS.Promise" locid="WinJS.UI.FlipView.count_returnValue">
                    /// A Promise that contains the number of items in the list
                    /// or WinJS.UI.CountResult.unknown if the count is unavailable.
                    /// </returns>
                    /// </signature>

                    _WriteProfilerMark("WinJS.UI.FlipView:count,info");
                    var that = this;
                    return new Promise(function (complete, error) {
                        if (that._itemsManager) {
                            if (that._pageManager._cachedSize === _UI.CountResult.unknown || that._pageManager._cachedSize >= 0) {
                                complete(that._pageManager._cachedSize);
                            } else {
                                that._dataSource.getCount().then(function (count) {
                                    that._pageManager._cachedSize = count;
                                    complete(count);
                                });
                            }
                        } else {
                            error(FlipView.noitemsManagerForCount);
                        }
                    });
                },

                setCustomAnimations: function FlipView_setCustomAnimations(animations) {
                    /// <signature helpKeyword="WinJS.UI.FlipView.setCustomAnimations">
                    /// <summary locid="WinJS.UI.FlipView.setCustomAnimations">
                    /// Sets custom animations for the FlipView to use when navigating between pages.
                    /// </summary>
                    /// <param name="animations" type="Object" locid="WinJS.UI.FlipView.setCustomAnimations_p:animations">
                    /// An object containing up to three fields, one for each navigation action: next, previous, and jump
                    /// Each of those fields must be a function with this signature: function (outgoingPage, incomingPage).
                    /// This function returns a WinJS.Promise object that completes once the animations are finished.
                    /// If a field is null or undefined, the FlipView reverts to its default animation for that action.
                    /// </param>
                    /// </signature>
                    _WriteProfilerMark("WinJS.UI.FlipView:setCustomAnimations,info");

                    if (animations.next !== undefined) {
                        this._nextAnimation = animations.next;
                    }
                    if (animations.previous !== undefined) {
                        this._prevAnimation = animations.previous;
                    }
                    if (animations.jump !== undefined) {
                        this._jumpAnimation = animations.jump;
                    }
                },

                forceLayout: function FlipView_forceLayout() {
                    /// <signature helpKeyword="WinJS.UI.FlipView.forceLayout">
                    /// <summary locid="WinJS.UI.FlipView.forceLayout">
                    /// Forces the FlipView to update its layout.
                    /// Use this function when making the FlipView visible again after its style.display property had been set to "none".
                    /// </summary>
                    /// </signature>
                    _WriteProfilerMark("WinJS.UI.FlipView:forceLayout,info");

                    this._pageManager.resized();
                },

                // Private members

                _initializeFlipView: function FlipView_initializeFlipView(element, isHorizontal, dataSource, itemRenderer, initialIndex, itemSpacing) {
                    var that = this;
                    var flipViewInitialized = false;
                    this._flipviewDiv = element;
                    _ElementUtilities.addClass(this._flipviewDiv, flipViewClass);
                    this._contentDiv = _Global.document.createElement("div");
                    this._panningDivContainer = _Global.document.createElement("div");
                    this._panningDivContainer.className = "win-surface";
                    this._panningDiv = _Global.document.createElement("div");
                    this._prevButton = _Global.document.createElement("button");
                    this._nextButton = _Global.document.createElement("button");
                    this._isHorizontal = isHorizontal;
                    this._dataSource = dataSource;
                    this._itemRenderer = itemRenderer;
                    this._itemsManager = null;
                    this._pageManager = null;

                    var stylesRequiredForFullFeatureMode = [
                        "scroll-limit-x-max",
                        "scroll-limit-x-min",
                        "scroll-limit-y-max",
                        "scroll-limit-y-min",
                        "scroll-snap-type",
                        "scroll-snap-x",
                        "scroll-snap-y",
                        "overflow-style",
                    ];

                    var allFeaturesSupported = true,
                        styleEquivalents = _BaseUtils._browserStyleEquivalents;
                    for (var i = 0, len = stylesRequiredForFullFeatureMode.length; i < len; i++) {
                        allFeaturesSupported = allFeaturesSupported && !!(styleEquivalents[stylesRequiredForFullFeatureMode[i]]);
                    }
                    allFeaturesSupported = allFeaturesSupported && !!_BaseUtils._browserEventEquivalents["manipulationStateChanged"];
                    allFeaturesSupported = allFeaturesSupported && _ElementUtilities._supportsSnapPoints;
                    this._environmentSupportsTouch = allFeaturesSupported;

                    var accName = this._flipviewDiv.getAttribute("aria-label");
                    if (!accName) {
                        this._flipviewDiv.setAttribute("aria-label", "");
                    }

                    this._flipviewDiv.setAttribute("role", "listbox");
                    if (!this._flipviewDiv.style.overflow) {
                        this._flipviewDiv.style.overflow = "hidden";
                    }
                    this._contentDiv.style.position = "relative";
                    this._contentDiv.style.zIndex = 0;
                    this._contentDiv.style.width = "100%";
                    this._contentDiv.style.height = "100%";
                    this._panningDiv.style.position = "relative";
                    this._panningDivContainer.style.position = "relative";
                    this._panningDivContainer.style.width = "100%";
                    this._panningDivContainer.style.height = "100%";
                    this._panningDivContainer.setAttribute("role", "group");
                    this._panningDivContainer.setAttribute("aria-label", strings.panningContainerAriaLabel);

                    this._contentDiv.appendChild(this._panningDivContainer);
                    this._flipviewDiv.appendChild(this._contentDiv);

                    this._panningDiv.style.width = "100%";
                    this._panningDiv.style.height = "100%";
                    this._setupOrientation();
                    function setUpButton(button) {
                        button.setAttribute("aria-hidden", true);
                        button.style.visibility = "hidden";
                        button.style.opacity = 0.0;
                        button.tabIndex = -1;
                        button.style.zIndex = 1000;
                    }
                    setUpButton(this._prevButton);
                    setUpButton(this._nextButton);
                    this._prevButton.setAttribute("aria-label", previousButtonLabel);
                    this._nextButton.setAttribute("aria-label", nextButtonLabel);
                    this._prevButton.setAttribute("type", "button");
                    this._nextButton.setAttribute("type", "button");
                    this._panningDivContainer.appendChild(this._panningDiv);
                    this._contentDiv.appendChild(this._prevButton);
                    this._contentDiv.appendChild(this._nextButton);

                    this._itemsManagerCallback = {
                        // Callbacks for itemsManager
                        inserted: function FlipView_inserted(itemPromise, previousHandle, nextHandle) {
                            that._itemsManager._itemFromPromise(itemPromise).then(function (element) {
                                var previous = that._itemsManager._elementFromHandle(previousHandle);
                                var next = that._itemsManager._elementFromHandle(nextHandle);
                                that._pageManager.inserted(element, previous, next, true);
                            });
                        },

                        countChanged: function FlipView_countChanged(newCount, oldCount) {
                            that._pageManager._cachedSize = newCount;

                            // Don't fire the datasourcecountchanged event when there is a state transition
                            if (oldCount !== _UI.CountResult.unknown) {
                                that._fireDatasourceCountChangedEvent();
                            }
                        },

                        changed: function FlipView_changed(newElement, oldElement) {
                            that._pageManager.changed(newElement, oldElement);
                        },

                        moved: function FlipView_moved(element, prev, next, itemPromise) {
                            var elementReady = function (element) {
                                that._pageManager.moved(element, prev, next);
                            };

                            // If we haven't instantiated this item yet, do so now
                            if (!element) {
                                that._itemsManager._itemFromPromise(itemPromise).then(elementReady);
                            } else {
                                elementReady(element);
                            }

                        },

                        removed: function FlipView_removed(element, mirage) {
                            if (element) {
                                that._pageManager.removed(element, mirage, true);
                            }
                        },

                        knownUpdatesComplete: function FlipView_knownUpdatesComplete() {
                        },

                        beginNotifications: function FlipView_beginNotifications() {
                            that._cancelAnimation();
                            that._pageManager.notificationsStarted();
                        },

                        endNotifications: function FlipView_endNotifications() {
                            that._pageManager.notificationsEnded();
                        },

                        itemAvailable: function FlipView_itemAvailable(real, placeholder) {
                            that._pageManager.itemRetrieved(real, placeholder);
                        },

                        reload: function FlipView_reload() {
                            that._pageManager.reload();
                        }
                    };

                    if (this._dataSource) {
                        this._itemsManager = _ItemsManager._createItemsManager(this._dataSource, this._itemRenderer, this._itemsManagerCallback, {
                            ownerElement: this._flipviewDiv
                        });
                    }

                    this._pageManager = new _PageManager._FlipPageManager(this._flipviewDiv, this._panningDiv, this._panningDivContainer, this._itemsManager, itemSpacing, this._environmentSupportsTouch,
                    {
                        hidePreviousButton: function () {
                            that._hasPrevContent = false;
                            that._fadeOutButton("prev");
                            that._prevButton.setAttribute("aria-hidden", true);
                        },

                        showPreviousButton: function () {
                            that._hasPrevContent = true;
                            that._fadeInButton("prev");
                            that._prevButton.setAttribute("aria-hidden", false);
                        },

                        hideNextButton: function () {
                            that._hasNextContent = false;
                            that._fadeOutButton("next");
                            that._nextButton.setAttribute("aria-hidden", true);
                        },

                        showNextButton: function () {
                            that._hasNextContent = true;
                            that._fadeInButton("next");
                            that._nextButton.setAttribute("aria-hidden", false);
                        }
                    });

                    this._pageManager.initialize(initialIndex, this._isHorizontal);

                    this._dataSource.getCount().then(function (count) {
                        that._pageManager._cachedSize = count;
                    });

                    this._prevButton.addEventListener("click", function () {
                        that.previous();
                    }, false);

                    this._nextButton.addEventListener("click", function () {
                        that.next();
                    }, false);

                    new _ElementUtilities._MutationObserver(flipViewPropertyChanged).observe(this._flipviewDiv, { attributes: true, attributeFilter: ["dir", "style"] });
                    this._cachedStyleDir = this._flipviewDiv.style.direction;

                    this._flipviewDiv.addEventListener("mselementresize", flipviewResized);
                    _ElementUtilities._resizeNotifier.subscribe(this._flipviewDiv, flipviewResized);

                    this._contentDiv.addEventListener("mouseleave", function () {
                        that._mouseInViewport = false;
                    }, false);

                    var PT_TOUCH = _ElementUtilities._MSPointerEvent.MSPOINTER_TYPE_TOUCH || "touch";
                    function handleShowButtons(e) {
                        if (e.pointerType !== PT_TOUCH) {
                            that._touchInteraction = false;
                            if (e.screenX === that._lastMouseX && e.screenY === that._lastMouseY) {
                                return;
                            }
                            that._lastMouseX = e.screenX;
                            that._lastMouseY = e.screenY;
                            that._mouseInViewport = true;
                            that._fadeInButton("prev");
                            that._fadeInButton("next");
                            that._fadeOutButtons();
                        }
                    }

                    function handlePointerDown(e) {
                        if (e.pointerType === PT_TOUCH) {
                            that._mouseInViewport = false;
                            that._touchInteraction = true;
                            that._fadeOutButtons(true);
                        } else {
                            that._touchInteraction = false;
                            if (!that._isInteractive(e.target)) {
                                // Disable the default behavior of the mouse wheel button to avoid auto-scroll
                                if ((e.buttons & 4) !== 0) {
                                    e.stopPropagation();
                                    e.preventDefault();
                                }
                            }
                        }
                    }

                    function handlePointerUp(e) {
                        if (e.pointerType !== PT_TOUCH) {
                            that._touchInteraction = false;
                        }
                    }

                    if (this._environmentSupportsTouch) {
                        _ElementUtilities._addEventListener(this._contentDiv, "pointerdown", handlePointerDown, false);
                        _ElementUtilities._addEventListener(this._contentDiv, "pointermove", handleShowButtons, false);
                        _ElementUtilities._addEventListener(this._contentDiv, "pointerup", handlePointerUp, false);
                    }

                    this._panningDivContainer.addEventListener("scroll", function () {
                        that._scrollPosChanged();
                    }, false);

                    this._panningDiv.addEventListener("blur", function () {
                        if (!that._touchInteraction) {
                            that._fadeOutButtons();
                        }
                    }, true);

                    // Scroll position isn't maintained when an element is added/removed from
                    // the DOM so every time we are placed back in, let the PageManager
                    // fix the scroll position.
                    var initiallyParented = _Global.document.body.contains(this._flipviewDiv);
                    _ElementUtilities._addInsertedNotifier(this._flipviewDiv);
                    this._flipviewDiv.addEventListener("WinJSNodeInserted", function (event) {
                        // WinJSNodeInserted fires even if the element is already in the DOM
                        if (initiallyParented) {
                            initiallyParented = false;
                            return;
                        }
                        that._pageManager.resized();
                    }, false);

                    this._flipviewDiv.addEventListener("keydown", function (event) {
                        var cancelBubbleIfHandled = true;
                        if (!that._isInteractive(event.target)) {
                            var Key = _ElementUtilities.Key,
                                handled = false;
                            if (that._isHorizontal) {
                                switch (event.keyCode) {
                                    case Key.leftArrow:
                                        (that._rtl ? that.next() : that.previous());
                                        handled = true;
                                        break;

                                    case Key.pageUp:
                                        that.previous();
                                        handled = true;
                                        break;

                                    case Key.rightArrow:
                                        (that._rtl ? that.previous() : that.next());
                                        handled = true;
                                        break;

                                    case Key.pageDown:
                                        that.next();
                                        handled = true;
                                        break;

                                        // Prevent scrolling pixel by pixel, but let the event bubble up
                                    case Key.upArrow:
                                    case Key.downArrow:
                                        handled = true;
                                        cancelBubbleIfHandled = false;
                                        break;
                                }
                            } else {
                                switch (event.keyCode) {
                                    case Key.upArrow:
                                    case Key.pageUp:
                                        that.previous();
                                        handled = true;
                                        break;

                                    case Key.downArrow:
                                    case Key.pageDown:
                                        that.next();
                                        handled = true;
                                        break;

                                    case Key.space:
                                        handled = true;
                                        break;
                                }
                            }

                            switch (event.keyCode) {
                                case Key.home:
                                    that.currentPage = 0;
                                    handled = true;
                                    break;

                                case Key.end:
                                    if (that._pageManager._cachedSize > 0) {
                                        that.currentPage = that._pageManager._cachedSize - 1;
                                    }
                                    handled = true;
                                    break;
                            }

                            if (handled) {
                                event.preventDefault();
                                if (cancelBubbleIfHandled) {
                                    event.stopPropagation();
                                }
                                return true;
                            }
                        }
                    }, false);

                    flipViewInitialized = true;
                },

                _windowWheelHandler: function FlipView_windowWheelHandler(ev) {
                    // When you are using the mouse wheel to scroll a horizontal area such as a WinJS.UI.Hub and one of the sections
                    // has a WinJS.UI.FlipView you may get stuck on that item. This logic is to allow a scroll event to skip the flipview's
                    // overflow scroll div and instead go to the parent scroller. We only skip the scroll wheel event for a fixed amount of time
                    ev = ev.detail.originalEvent;
                    var wheelWithinFlipper = ev.target && (this._flipviewDiv.contains(ev.target) || this._flipviewDiv === ev.target);
                    var that = this;
                    var now = _BaseUtils._now();
                    var withinAvoidTime = this._avoidTrappingTime > now;

                    if (!wheelWithinFlipper || withinAvoidTime) {
                        this._avoidTrappingTime = now + avoidTrapDelay;
                    }

                    if (wheelWithinFlipper && withinAvoidTime) {
                        this._panningDivContainer.style["overflowX"] = "hidden";
                        this._panningDivContainer.style["overflowY"] = "hidden";
                        _BaseUtils._yieldForDomModification(function () {
                            // Avoid being stuck between items
                            that._pageManager._ensureCentered();

                            if (that._isHorizontal) {
                                that._panningDivContainer.style["overflowX"] = (that._environmentSupportsTouch ? "scroll" : "hidden");
                                that._panningDivContainer.style["overflowY"] = "hidden";
                            } else {
                                that._panningDivContainer.style["overflowY"] = (that._environmentSupportsTouch ? "scroll" : "hidden");
                                that._panningDivContainer.style["overflowX"] = "hidden";
                            }
                        });
                    } else if (wheelWithinFlipper) {
                        this._pageManager.simulateMouseWheelScroll(ev);
                    }
                },

                _isInteractive: function FlipView_isInteractive(element) {
                    if (element.parentNode) {
                        var matches = element.parentNode.querySelectorAll(".win-interactive, .win-interactive *");
                        for (var i = 0, len = matches.length; i < len; i++) {
                            if (matches[i] === element) {
                                return true;
                            }
                        }
                    }
                    return false;
                },

                _refreshHandler: function FlipView_refreshHandler() {
                    var dataSource = this._dataSourceAfterRefresh || this._dataSource,
                        renderer = this._itemRendererAfterRefresh || this._itemRenderer,
                        initialIndex = this._indexAfterRefresh || 0;
                    this._setDatasource(dataSource, renderer, initialIndex);
                    this._dataSourceAfterRefresh = null;
                    this._itemRendererAfterRefresh = null;
                    this._indexAfterRefresh = 0;
                    this._refreshTimer = false;
                },

                _refresh: function FlipView_refresh() {
                    if (!this._refreshTimer) {
                        var that = this;
                        this._refreshTimer = true;
                        // Batch calls to _refresh
                        Scheduler.schedule(function FlipView_refreshHandler() {
                            if (that._refreshTimer && !that._disposed) {
                                that._refreshHandler();
                            }
                        }, Scheduler.Priority.high, null, "WinJS.UI.FlipView._refreshHandler");
                    }
                },

                _getItemRenderer: function FlipView_getItemRenderer(itemTemplate) {
                    var itemRenderer = null;
                    if (typeof itemTemplate === "function") {
                        var itemPromise = new Promise(function () { });
                        var itemTemplateResult = itemTemplate(itemPromise);
                        if (itemTemplateResult.element) {
                            if (typeof itemTemplateResult.element === "object" && typeof itemTemplateResult.element.then === "function") {
                                // This renderer returns a promise to an element
                                itemRenderer = function (itemPromise) {
                                    var elementRoot = _Global.document.createElement("div");
                                    elementRoot.className = "win-template";
                                    _Dispose.markDisposable(elementRoot);
                                    return {
                                        element: elementRoot,
                                        renderComplete: itemTemplate(itemPromise).element.then(function (element) {
                                            elementRoot.appendChild(element);
                                        })
                                    };
                                };
                            } else {
                                // This renderer already returns a placeholder
                                itemRenderer = itemTemplate;
                            }
                        } else {
                            // Return a renderer that has return a placeholder
                            itemRenderer = function (itemPromise) {
                                var elementRoot = _Global.document.createElement("div");
                                elementRoot.className = "win-template";
                                _Dispose.markDisposable(elementRoot);
                                // The pagecompleted event relies on this elementRoot
                                // to ensure that we are still looking at the same
                                // item after the render completes.
                                return {
                                    element: elementRoot,
                                    renderComplete: itemPromise.then(function () {
                                        return Promise.as(itemTemplate(itemPromise)).then(function (element) {
                                            elementRoot.appendChild(element);
                                        });
                                    })
                                };
                            };
                        }
                    } else if (typeof itemTemplate === "object") {
                        itemRenderer = itemTemplate.renderItem;
                    }
                    return itemRenderer;
                },

                _navigate: function FlipView_navigate(goForward, cancelAnimationCallback) {
                    if (_BaseUtils.validation && this._refreshTimer) {
                        throw new _ErrorFromName("WinJS.UI.FlipView.NavigationDuringStateChange", strings.navigationDuringStateChange);
                    }

                    if (!this._animating) {
                        this._animatingForward = goForward;
                    }
                    this._goForward = goForward;

                    if (this._animating && !this._cancelAnimation()) {
                        return false;
                    }
                    var that = this;
                    var customAnimation = (goForward ? this._nextAnimation : this._prevAnimation),
                        animation = (customAnimation ? customAnimation : this._defaultAnimation.bind(this)),
                        completionCallback = function (goForward) { that._completeNavigation(goForward); },
                        elements = this._pageManager.startAnimatedNavigation(goForward, cancelAnimationCallback, completionCallback);
                    if (elements) {
                        this._animationsStarted();
                        var outgoingElement = elements.outgoing.pageRoot,
                            incomingElement = elements.incoming.pageRoot;
                        this._contentDiv.appendChild(outgoingElement);
                        this._contentDiv.appendChild(incomingElement);

                        this._completeNavigationPending = true;
                        animation(outgoingElement, incomingElement).then(function () {
                            if (that._completeNavigationPending) {
                                completionCallback(that._goForward);
                            }
                        }).done();
                        return true;
                    } else {
                        return false;
                    }
                },

                _cancelDefaultAnimation: function FlipView_cancelDefaultAnimation(outgoingElement, incomingElement) {
                    // Cancel the fadeOut animation
                    outgoingElement.style.opacity = 0;

                    // Cancel the enterContent animation
                    incomingElement.style.animationName = "";
                    incomingElement.style.opacity = 1;
                },

                _cancelAnimation: function FlipView_cancelAnimation() {
                    if (this._pageManager._navigationAnimationRecord &&
                        this._pageManager._navigationAnimationRecord.completionCallback) {

                        var cancelCallback = this._pageManager._navigationAnimationRecord.cancelAnimationCallback;
                        if (cancelCallback) {
                            cancelCallback = cancelCallback.bind(this);
                        }

                        if (this._pageManager._navigationAnimationRecord && this._pageManager._navigationAnimationRecord.elementContainers) {
                            var outgoingPage = this._pageManager._navigationAnimationRecord.elementContainers[0],
                            incomingPage = this._pageManager._navigationAnimationRecord.elementContainers[1],
                            outgoingElement = outgoingPage.pageRoot,
                            incomingElement = incomingPage.pageRoot;

                            // Invoke the function that will cancel the animation
                            if (cancelCallback) {
                                cancelCallback(outgoingElement, incomingElement);
                            }

                            // Invoke the completion function after cancelling the animation
                            this._pageManager._navigationAnimationRecord.completionCallback(this._animatingForward);

                            return true;
                        }
                    }
                    return false;
                },

                _completeNavigation: function FlipView_completeNavigation(goForward) {
                    if (this._disposed) {
                        return;
                    }

                    this._pageManager._resizing = false;
                    if (this._pageManager._navigationAnimationRecord &&
                        this._pageManager._navigationAnimationRecord.elementContainers) {

                        var outgoingPage = this._pageManager._navigationAnimationRecord.elementContainers[0],
                            incomingPage = this._pageManager._navigationAnimationRecord.elementContainers[1],
                            outgoingElement = outgoingPage.pageRoot,
                            incomingElement = incomingPage.pageRoot;

                        if (outgoingElement.parentNode) {
                            outgoingElement.parentNode.removeChild(outgoingElement);
                        }
                        if (incomingElement.parentNode) {
                            incomingElement.parentNode.removeChild(incomingElement);
                        }
                        this._pageManager.endAnimatedNavigation(goForward, outgoingPage, incomingPage);
                        this._fadeOutButtons();
                        this._scrollPosChanged();
                        this._pageManager._ensureCentered(true);
                        this._animationsFinished();
                    }
                    this._completeNavigationPending = false;
                },

                _completeJump: function FlipView_completeJump() {
                    if (this._disposed) {
                        return;
                    }

                    this._pageManager._resizing = false;
                    if (this._pageManager._navigationAnimationRecord &&
                        this._pageManager._navigationAnimationRecord.elementContainers) {

                        var outgoingPage = this._pageManager._navigationAnimationRecord.elementContainers[0],
                            incomingPage = this._pageManager._navigationAnimationRecord.elementContainers[1],
                            outgoingElement = outgoingPage.pageRoot,
                            incomingElement = incomingPage.pageRoot;

                        if (outgoingElement.parentNode) {
                            outgoingElement.parentNode.removeChild(outgoingElement);
                        }
                        if (incomingElement.parentNode) {
                            incomingElement.parentNode.removeChild(incomingElement);
                        }

                        this._pageManager.endAnimatedJump(outgoingPage, incomingPage);
                        this._animationsFinished();
                    }
                    this._completeJumpPending = false;
                },

                _resize: function FlipView_resize() {
                    this._pageManager.resized();
                },

                _setCurrentIndex: function FlipView_setCurrentIndex(index) {
                    return this._pageManager.jumpToIndex(index);
                },

                _getCurrentIndex: function FlipView_getCurrentIndex() {
                    return this._pageManager.currentIndex();
                },

                _setDatasource: function FlipView_setDatasource(source, template, index) {
                    if (this._animating) {
                        this._cancelAnimation();
                    }

                    var initialIndex = 0;
                    if (index !== undefined) {
                        initialIndex = index;
                    }
                    this._dataSource = source;
                    this._itemRenderer = template;
                    var oldItemsManager = this._itemsManager;
                    this._itemsManager = _ItemsManager._createItemsManager(this._dataSource, this._itemRenderer, this._itemsManagerCallback, {
                        ownerElement: this._flipviewDiv
                    });
                    this._dataSource = this._itemsManager.dataSource;

                    var that = this;
                    this._dataSource.getCount().then(function (count) {
                        that._pageManager._cachedSize = count;
                    });
                    this._pageManager.setNewItemsManager(this._itemsManager, initialIndex);
                    oldItemsManager && oldItemsManager.release();
                },

                _fireDatasourceCountChangedEvent: function FlipView_fireDatasourceCountChangedEvent() {
                    var that = this;
                    Scheduler.schedule(function FlipView_dispatchDataSourceCountChangedEvent() {
                        var event = _Global.document.createEvent("Event");
                        event.initEvent(FlipView.datasourceCountChangedEvent, true, true);
                        _WriteProfilerMark("WinJS.UI.FlipView:dataSourceCountChangedEvent,info");
                        that._flipviewDiv.dispatchEvent(event);
                    }, Scheduler.Priority.normal, null, "WinJS.UI.FlipView._dispatchDataSourceCountChangedEvent");
                },

                _scrollPosChanged: function FlipView_scrollPosChanged() {
                    this._pageManager.scrollPosChanged();
                },

                _axisAsString: function FlipView_axisAsString() {
                    return (this._isHorizontal ? "horizontal" : "vertical");
                },

                _setupOrientation: function FlipView_setupOrientation() {
                    if (this._isHorizontal) {
                        this._panningDivContainer.style["overflowX"] = (this._environmentSupportsTouch ? "scroll" : "hidden");
                        this._panningDivContainer.style["overflowY"] = "hidden";
                        var rtl = _Global.getComputedStyle(this._flipviewDiv, null).direction === "rtl";
                        this._rtl = rtl;
                        if (rtl) {
                            this._prevButton.className = navButtonClass + " " + navButtonRightClass;
                            this._nextButton.className = navButtonClass + " " + navButtonLeftClass;
                        } else {
                            this._prevButton.className = navButtonClass + " " + navButtonLeftClass;
                            this._nextButton.className = navButtonClass + " " + navButtonRightClass;
                        }
                        this._prevButton.innerHTML = (rtl ? rightArrowGlyph : leftArrowGlyph);
                        this._nextButton.innerHTML = (rtl ? leftArrowGlyph : rightArrowGlyph);
                    } else {
                        this._panningDivContainer.style["overflowY"] = (this._environmentSupportsTouch ? "scroll" : "hidden");
                        this._panningDivContainer.style["overflowX"] = "hidden";
                        this._prevButton.className = navButtonClass + " " + navButtonTopClass;
                        this._nextButton.className = navButtonClass + " " + navButtonBottomClass;
                        this._prevButton.innerHTML = topArrowGlyph;
                        this._nextButton.innerHTML = bottomArrowGlyph;
                    }
                    this._panningDivContainer.style["msOverflowStyle"] = "none";
                },

                _fadeInButton: function FlipView_fadeInButton(button, forceShow) {
                    if (this._mouseInViewport || forceShow || !this._environmentSupportsTouch) {
                        if (button === "next" && this._hasNextContent) {
                            if (this._nextButtonAnimation) {
                                this._nextButtonAnimation.cancel();
                                this._nextButtonAnimation = null;
                            }

                            this._nextButton.style.visibility = "visible";
                            this._nextButtonAnimation = this._fadeInFromCurrentValue(this._nextButton);
                        } else if (button === "prev" && this._hasPrevContent) {
                            if (this._prevButtonAnimation) {
                                this._prevButtonAnimation.cancel();
                                this._prevButtonAnimation = null;
                            }

                            this._prevButton.style.visibility = "visible";
                            this._prevButtonAnimation = this._fadeInFromCurrentValue(this._prevButton);
                        }
                    }
                },

                _fadeOutButton: function FlipView_fadeOutButton(button) {
                    var that = this;
                    if (button === "next") {
                        if (this._nextButtonAnimation) {
                            this._nextButtonAnimation.cancel();
                            this._nextButtonAnimation = null;
                        }

                        this._nextButtonAnimation = Animations.fadeOut(this._nextButton).
                            then(function () {
                                that._nextButton.style.visibility = "hidden";
                            });
                        return this._nextButtonAnimation;
                    } else {
                        if (this._prevButtonAnimation) {
                            this._prevButtonAnimation.cancel();
                            this._prevButtonAnimation = null;
                        }

                        this._prevButtonAnimation = Animations.fadeOut(this._prevButton).
                            then(function () {
                                that._prevButton.style.visibility = "hidden";
                            });
                        return this._prevButtonAnimation;
                    }
                },

                _fadeOutButtons: function FlipView_fadeOutButtons(immediately) {
                    if (!this._environmentSupportsTouch) {
                        return;
                    }

                    if (this._buttonFadePromise) {
                        this._buttonFadePromise.cancel();
                        this._buttonFadePromise = null;
                    }

                    var that = this;
                    this._buttonFadePromise = (immediately ? Promise.wrap() : Promise.timeout(_TransitionAnimation._animationTimeAdjustment(buttonFadeDelay))).then(function () {
                        that._fadeOutButton("prev");
                        that._fadeOutButton("next");
                        that._buttonFadePromise = null;
                    });
                },

                _animationsStarted: function FlipView_animationsStarted() {
                    this._animating = true;
                },

                _animationsFinished: function FlipView_animationsFinished() {
                    this._animating = false;
                },

                _defaultAnimation: function FlipView_defaultAnimation(curr, next) {
                    var incomingPageMove = {};
                    next.style.left = "0px";
                    next.style.top = "0px";
                    next.style.opacity = 0.0;
                    var pageDirection = ((curr.itemIndex > next.itemIndex) ? -animationMoveDelta : animationMoveDelta);
                    incomingPageMove.left = (this._isHorizontal ? (this._rtl ? -pageDirection : pageDirection) : 0) + "px";
                    incomingPageMove.top = (this._isHorizontal ? 0 : pageDirection) + "px";
                    var fadeOutPromise = Animations.fadeOut(curr),
                        enterContentPromise = Animations.enterContent(next, [incomingPageMove], { mechanism: "transition" });
                    return Promise.join([fadeOutPromise, enterContentPromise]);
                },

                _fadeInFromCurrentValue: function FlipView_fadeInFromCurrentValue(shown) {
                    // Intentionally not using the PVL fadeIn animation because we don't want
                    // to start always from 0 in some cases
                    return _TransitionAnimation.executeTransition(
                        shown,
                        {
                            property: "opacity",
                            delay: 0,
                            duration: 167,
                            timing: "linear",
                            to: 1
                        });
                }
            }, _Constants);

            _Base.Class.mix(FlipView, _Events.createEventProperties(
                FlipView.datasourceCountChangedEvent,
                FlipView.pageVisibilityChangedEvent,
                FlipView.pageSelectedEvent,
                FlipView.pageCompletedEvent));
            _Base.Class.mix(FlipView, _Control.DOMEventMixin);

            return FlipView;
        })
    });

});
// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/ItemContainer',[
    'exports',
    '../Core/_Global',
    '../Core/_Base',
    '../Core/_BaseUtils',
    '../Core/_ErrorFromName',
    '../Core/_Events',
    '../Core/_Log',
    '../Core/_Resources',
    '../Core/_WriteProfilerMark',
    '../Promise',
    '../Scheduler',
    '../Utilities/_Control',
    '../Utilities/_Dispose',
    '../Utilities/_ElementUtilities',
    '../Utilities/_Hoverable',
    '../Utilities/_KeyboardBehavior',
    '../Utilities/_UI',
    './ItemContainer/_Constants',
    './ItemContainer/_ItemEventsHandler'
    ], function itemContainerInit(exports, _Global, _Base, _BaseUtils, _ErrorFromName, _Events, _Log, _Resources, _WriteProfilerMark, Promise, Scheduler, _Control, _Dispose, _ElementUtilities, _Hoverable, _KeyboardBehavior, _UI, _Constants, _ItemEventsHandler) {
    "use strict";

    var createEvent = _Events._createEventProperty;
    var eventNames = {
        invoked: "invoked",
        selectionchanging: "selectionchanging",
        selectionchanged: "selectionchanged"
    };

    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {
        /// <field>
        /// <summary locid="WinJS.UI.ItemContainer">
        /// Defines an item that can be pressed, swiped, and dragged.
        /// </summary>
        /// </field>
        /// <icon src="ui_winjs.ui.itemcontainer.12x12.png" width="12" height="12" />
        /// <icon src="ui_winjs.ui.itemcontainer.16x16.png" width="16" height="16" />
        /// <htmlSnippet supportsContent="true"><![CDATA[
        /// <div data-win-control="WinJS.UI.ItemContainer" data-win-options="{swipeBehavior: 'select'}">HTML content</div>
        /// ]]></htmlSnippet>
        /// <event name="invoked" bubbles="true" locid="WinJS.UI.ItemContainer_e:invoked">Raised when the user taps or clicks the item.</event>
        /// <event name="selectionchanging" bubbles="true" locid="WinJS.UI.ItemContainer_e:selectionchanging">Raised before the item is selected or deselected.</event>
        /// <event name="selectionchanged" bubbles="true" locid="WinJS.UI.ItemContainer_e:selectionchanged">Raised after the item is selected or deselected.</event>
        /// <part name="itemcontainer" class="win-itemcontainer" locid="WinJS.UI.ItemContainer_part:itemcontainer">Main container for the selection item control.</part>
        /// <part name="selectionbackground" class="win-selectionbackground" locid="WinJS.UI.ItemContainer_part:selectionbackground">The background of a selection checkmark.</part>
        /// <part name="selectioncheckmark" class="win-selectioncheckmark" locid="WinJS.UI.ItemContainer_part:selectioncheckmark">A selection checkmark.</part>
        /// <part name="focusedoutline" class="win-focusedoutline" locid="WinJS.UI.ItemContainer_part:focusedoutline">Used to display an outline when the main container has keyboard focus.</part>
        /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
        /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
        /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
        ItemContainer: _Base.Namespace._lazy(function () {
            var strings = {
                get duplicateConstruction() { return "Invalid argument: Controls may only be instantiated one time for each DOM element"; }
            };

            var ItemContainer = _Base.Class.define(function ItemContainer_ctor(element, options) {
                /// <signature helpKeyword="WinJS.UI.ItemContainer.ItemContainer">
                /// <summary locid="WinJS.UI.ItemContainer.constructor">
                /// Creates a new ItemContainer control.
                /// </summary>
                /// <param name="element" type="HTMLElement" domElement="true" isOptional="true" locid="WinJS.UI.ItemContainer.constructor_p:element">
                /// The DOM element that hosts the ItemContainer control.
                /// </param>
                /// <param name="options" type="Object" isOptional="true" locid="WinJS.UI.ItemContainer.constructor_p:options">
                /// An object that contains one or more property/value pairs to apply to the new control.
                /// Each property of the options object corresponds to one of the control's properties or events.
                /// Event names must begin with "on". For example, to provide a handler for the selectionchanging event,
                /// add a property named "onselectionchanging" to the options object and set its value to the event handler.
                /// </param>
                /// <returns type="WinJS.UI.ItemContainer" locid="WinJS.UI.ItemContainer.constructor_returnValue">
                /// The new ItemContainer control.
                /// </returns>
                /// </signature>
                element = element || _Global.document.createElement("DIV");
                this._id = element.id || _ElementUtilities._uniqueID(element);
                this._writeProfilerMark("constructor,StartTM");

                options = options || {};

                if (element.winControl) {
                    throw new _ErrorFromName("WinJS.UI.ItemContainer.DuplicateConstruction", strings.duplicateConstruction);
                }

                // Attaching JS control to DOM element
                element.winControl = this;

                this._element = element;
                _ElementUtilities.addClass(element, "win-disposable");
                this._selectionMode = _UI.SelectionMode.single;
                this._draggable = false;
                this._pressedEntity = { type: _UI.ObjectType.item, index: _Constants._INVALID_INDEX };

                this.tapBehavior = _UI.TapBehavior.invokeOnly;
                this.swipeOrientation = _UI.Orientation.vertical;
                this.swipeBehavior = _UI.SwipeBehavior.select;

                _ElementUtilities.addClass(this.element, ItemContainer._ClassName.itemContainer + " " + _Constants._containerClass);

                this._setupInternalTree();

                this._selection = new exports._SingleItemSelectionManager(element, this._itemBox);
                this._setTabIndex();

                _Control.setOptions(this, options);

                this._mutationObserver = new _ElementUtilities._MutationObserver(this._itemPropertyChange.bind(this));
                this._mutationObserver.observe(element, { attributes: true, attributeFilter: ["aria-selected"] });
                this._setAriaRole();

                var that = this;
                if (!this.selectionDisabled) {
                    Scheduler.schedule(function ItemContainer_async_initialize() {
                        that._setDirectionClass();
                    }, Scheduler.Priority.normal, null, "WinJS.UI.ItemContainer_async_initialize");
                }
                this._itemEventsHandler = new _ItemEventsHandler._ItemEventsHandler(Object.create({
                    containerFromElement: function () {
                        return that.element;
                    },
                    indexForItemElement: function () {
                        return 1;
                    },
                    indexForHeaderElement: function () {
                        return _Constants._INVALID_INDEX;
                    },
                    itemBoxAtIndex: function () {
                        return that._itemBox;
                    },
                    itemAtIndex: function () {
                        return that.element;
                    },
                    headerAtIndex: function () {
                        return null;
                    },
                    containerAtIndex: function () {
                        return that.element;
                    },
                    isZombie: function () {
                        return this._disposed;
                    },
                    getItemPosition: function () {
                        return that._getItemPosition();
                    },
                    rtl: function () {
                        return that._rtl();
                    },
                    fireInvokeEvent: function () {
                        that._fireInvokeEvent();
                    },
                    verifySelectionAllowed: function () {
                        return that._verifySelectionAllowed();
                    },
                    changeFocus: function () { },
                    selectRange: function (firstIndex, lastIndex) {
                        return that._selection.set({ firstIndex: firstIndex, lastIndex: lastIndex });
                    }
                }, {
                    pressedEntity: {
                        get: function () {
                            return that._pressedEntity;
                        },
                        set: function (value) {
                            that._pressedEntity = value;
                        }
                    },
                    pressedElement: {
                        enumerable: true,
                        set: function (value) {
                            that._pressedElement = value;
                        }
                    },
                    eventHandlerRoot: {
                        enumerable: true,
                        get: function () {
                            return that.element;
                        }
                    },
                    swipeBehavior: {
                        enumerable: true,
                        get: function () {
                            return that._swipeBehavior;
                        }
                    },
                    selectionMode: {
                        enumerable: true,
                        get: function () {
                            return that._selectionMode;
                        }
                    },
                    accessibleItemClass: {
                        enumerable: true,
                        get: function () {
                            // CSS class of the element with the aria role
                            return _Constants._containerClass;
                        }
                    },
                    canvasProxy: {
                        enumerable: true,
                        get: function () {
                            return that._captureProxy;
                        }
                    },
                    tapBehavior: {
                        enumerable: true,
                        get: function () {
                            return that._tapBehavior;
                        }
                    },
                    draggable: {
                        enumerable: true,
                        get: function () {
                            return that._draggable;
                        }
                    },
                    selection: {
                        enumerable: true,
                        get: function () {
                            return that._selection;
                        }
                    },
                    horizontal: {
                        enumerable: true,
                        get: function () {
                            return that._swipeOrientation === _UI.Orientation.vertical;
                        }
                    },
                    customFootprintParent: {
                        enumerable: true,
                        get: function () {
                            // Use the main container as the footprint
                            return null;
                        }
                    },
                    skipPreventDefaultOnPointerDown: {
                        enumerable: true,
                        get: function () {
                            return true;
                        }
                    }
                }));

                function eventHandler(eventName, caseSensitive, capture) {
                    return {
                        name: (caseSensitive ? eventName : eventName.toLowerCase()),
                        handler: function (eventObject) {
                            that["_on" + eventName](eventObject);
                        },
                        capture: capture
                    };
                }
                var events = [
                    eventHandler("MSManipulationStateChanged", true, true),
                    eventHandler("PointerDown"),
                    eventHandler("Click"),
                    eventHandler("PointerUp"),
                    eventHandler("PointerCancel"),
                    eventHandler("LostPointerCapture"),
                    eventHandler("ContextMenu"),
                    eventHandler("MSHoldVisual", true),
                    eventHandler("FocusIn"),
                    eventHandler("FocusOut"),
                    eventHandler("DragStart"),
                    eventHandler("DragEnd"),
                    eventHandler("KeyDown")
                ];
                events.forEach(function (eventHandler) {
                    _ElementUtilities._addEventListener(that.element, eventHandler.name, eventHandler.handler, !!eventHandler.capture);
                });

                this._writeProfilerMark("constructor,StopTM");
            }, {
                /// <field type="HTMLElement" domElement="true" hidden="true" locid="WinJS.UI.ItemContainer.element" helpKeyword="WinJS.UI.ItemContainer.element">
                /// Gets the DOM element that hosts the itemContainer control.
                /// </field>
                element: {
                    get: function () {
                        return this._element;
                    }
                },

                /// <field type="Boolean" locid="WinJS.UI.ItemContainer.draggable" helpKeyword="WinJS.UI.ItemContainer.draggable">
                /// Gets or sets a value that specifies whether the item can be dragged. The default value is false.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                draggable: {
                    get: function () {
                        return this._draggable;
                    },

                    set: function (value) {
                        if (_BaseUtils.isPhone) {
                            return;
                        }
                        if (this._draggable !== value) {
                            this._draggable = value;
                            this._updateDraggableAttribute();
                        }
                    }
                },

                /// <field type="Boolean" locid="WinJS.UI.ItemContainer.selected" helpKeyword="WinJS.UI.ItemContainer.selected">
                /// Gets or sets a value that specifies whether the item is selected.
                /// </field>
                selected: {
                    get: function () {
                        return this._selection.selected;
                    },

                    set: function (value) {
                        if (this._selection.selected !== value) {
                            this._selection.selected = value;
                        }
                    }
                },

                /// <field type="String" oamOptionsDatatype="WinJS.UI.Orientation" locid="WinJS.UI.ItemContainer.swipeOrientation" helpKeyword="WinJS.UI.ItemContainer.swipeOrientation">
                /// Gets or sets the swipe orientation of the ItemContainer control.
                /// The default value is "vertical".
                /// </field>
                swipeOrientation: {
                    get: function () {
                        return this._swipeOrientation;
                    },
                    set: function (value) {
                        if (value === _UI.Orientation.vertical) {
                            _ElementUtilities.removeClass(this.element, ItemContainer._ClassName.horizontal);
                            _ElementUtilities.addClass(this.element, ItemContainer._ClassName.vertical);
                        } else {
                            value = _UI.Orientation.horizontal;
                            _ElementUtilities.removeClass(this.element, ItemContainer._ClassName.vertical);
                            _ElementUtilities.addClass(this.element, ItemContainer._ClassName.horizontal);
                        }
                        this._swipeOrientation = value;
                    }
                },

                /// <field type="String" oamOptionsDatatype="WinJS.UI.TapBehavior" locid="WinJS.UI.ItemContainer.tapBehavior" helpKeyword="WinJS.UI.ItemContainer.tapBehavior">
                /// Gets or sets how the ItemContainer control reacts when the user taps or clicks an item.
                /// The tap or click can invoke the item, select it and invoke it, or have no effect.
                /// Possible values: "toggleSelect", "invokeOnly", and "none". The default value is "invokeOnly".
                /// </field>
                tapBehavior: {
                    get: function () {
                        return this._tapBehavior;
                    },
                    set: function (value) {
                        if (_BaseUtils.isPhone && value === _UI.TapBehavior.directSelect) {
                            return;
                        }
                        this._tapBehavior = value;
                        this._setAriaRole();
                    }
                },

                /// <field type="String" oamOptionsDatatype="WinJS.UI.SwipeBehavior" locid="WinJS.UI.ItemContainer.swipeBehavior" helpKeyword="WinJS.UI.ItemContainer.swipeBehavior">
                /// Gets or sets how the ItemContainer control reacts to the swipe interaction.
                /// The swipe gesture can select the item or it can have no effect on the current selection.
                /// Possible values: "select", "none". The default value is: "select".
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                swipeBehavior: {
                    get: function () {
                        return this._swipeBehavior;
                    },
                    set: function (value) {
                        this._swipeBehavior = value;
                        this._setSwipeClass();
                    }
                },

                /// <field type="Boolean" locid="WinJS.UI.ItemContainer.selectionDisabled" helpKeyword="WinJS.UI.ItemContainer.selectionDisabled">
                /// Gets or sets whether the item selection is disabled. The default value is false.
                /// </field>
                selectionDisabled: {
                    get: function () {
                        return this._selectionMode === _UI.SelectionMode.none;
                    },

                    set: function (value) {
                        if (value) {
                            this._selectionMode = _UI.SelectionMode.none;
                        } else {
                            this._setDirectionClass();
                            this._selectionMode = _UI.SelectionMode.single;
                        }
                        this._setSwipeClass();
                        this._setAriaRole();
                    }
                },

                /// <field type="Function" locid="WinJS.UI.ItemCotrol.oninvoked" helpKeyword="WinJS.UI.ItemCotrol.oninvoked">
                /// Raised when the item is invoked. You can use the tapBehavior property to specify whether taps and clicks invoke the item.
                /// </field>
                oninvoked: createEvent(eventNames.invoked),

                /// <field type="Function" locid="WinJS.UI.ItemCotrol.onselectionchanging" helpKeyword="WinJS.UI.ItemCotrol.onselectionchanging">
                /// Raised just before the item is selected or deselected.
                /// </field>
                onselectionchanging: createEvent(eventNames.selectionchanging),

                /// <field type="Function" locid="WinJS.UI.ItemCotrol.onselectionchanged" helpKeyword="WinJS.UI.ItemCotrol.onselectionchanged">
                /// Raised after the item is selected or deselected.
                /// </field>
                onselectionchanged: createEvent(eventNames.selectionchanged),

                forceLayout: function () {
                    /// <signature helpKeyword="WinJS.UI.ItemContainer.forceLayout">
                    /// <summary locid="WinJS.UI.ItemContainer.forceLayout">
                    /// Forces the ItemContainer control to update its layout.
                    /// Use this function when the reading direction  of the app changes after the control has been initialized.
                    /// </summary>
                    /// </signature>
                    this._forceLayout();
                },

                dispose: function () {
                    /// <signature helpKeyword="WinJS.UI.ItemContainer.dispose">
                    /// <summary locid="WinJS.UI.ItemContainer.dispose">
                    /// Disposes this control.
                    /// </summary>
                    /// </signature>

                    if (this._disposed) {
                        return;
                    }
                    this._disposed = true;

                    this._itemEventsHandler.dispose();
                    _Dispose.disposeSubTree(this.element);
                },

                _onMSManipulationStateChanged: function ItemContainer_onMSManipulationStateChanged(eventObject) {
                    this._itemEventsHandler.onMSManipulationStateChanged(eventObject);
                },

                _onPointerDown: function ItemContainer_onPointerDown(eventObject) {
                    this._itemEventsHandler.onPointerDown(eventObject);
                },

                _onClick: function ItemContainer_onClick(eventObject) {
                    this._itemEventsHandler.onClick(eventObject);
                },

                _onPointerUp: function ItemContainer_onPointerUp(eventObject) {
                    if (_ElementUtilities.hasClass(this._itemBox, _Constants._itemFocusClass)) {
                        this._onFocusOut(eventObject);
                    }
                    this._itemEventsHandler.onPointerUp(eventObject);
                },

                _onPointerCancel: function ItemContainer_onPointerCancel(eventObject) {
                    this._itemEventsHandler.onPointerCancel(eventObject);
                },

                _onLostPointerCapture: function ItemContainer_onLostPointerCapture(eventObject) {
                    this._itemEventsHandler.onLostPointerCapture(eventObject);
                },

                _onContextMenu: function ItemContainer_onContextMenu(eventObject) {
                    this._itemEventsHandler.onContextMenu(eventObject);
                },

                _onMSHoldVisual: function ItemContainer_onMSHoldVisual(eventObject) {
                    this._itemEventsHandler.onMSHoldVisual(eventObject);
                },

                _onFocusIn: function ItemContainer_onFocusIn() {
                    if (this._itemBox.querySelector("." + _Constants._itemFocusOutlineClass) || !_KeyboardBehavior._keyboardSeenLast) {
                        return;
                    }
                    _ElementUtilities.addClass(this._itemBox, _Constants._itemFocusClass);
                    var outline = _Global.document.createElement("div");
                    outline.className = _Constants._itemFocusOutlineClass;
                    this._itemBox.appendChild(outline);
                },

                _onFocusOut: function ItemContainer_onFocusOut() {
                    _ElementUtilities.removeClass(this._itemBox, _Constants._itemFocusClass);
                    var outline = this._itemBox.querySelector("." + _Constants._itemFocusOutlineClass);
                    if (outline) {
                        outline.parentNode.removeChild(outline);
                    }
                },

                _onDragStart: function ItemContainer_onDragStart(eventObject) {
                    // Drag shouldn't be initiated when the user holds down the mouse on a win-interactive element and moves.
                    // The problem is that the dragstart event's srcElement+target will both be an itembox (which has draggable=true), so we can't check for win-interactive in the dragstart event handler.
                    // The itemEventsHandler sets our _pressedElement field on PointerDown, so we use that instead when checking for interactive.
                    if (this._pressedElement && this._itemEventsHandler._isInteractive(this._pressedElement)) {
                        eventObject.preventDefault();
                    } else {
                        this._dragging = true;
                        var that = this;

                        // Firefox requires setData to be called on the dataTransfer object in order for DnD to continue.
                        // Firefox also has an issue rendering the item's itemBox+element, so we need to use setDragImage, using the item's container, to get it to render.
                        eventObject.dataTransfer.setData("text", "");
                        if (eventObject.dataTransfer.setDragImage) {
                            var rect = this.element.getBoundingClientRect();
                            eventObject.dataTransfer.setDragImage(this.element, eventObject.clientX - rect.left, eventObject.clientY - rect.top);
                        }
                        // We delay setting the win-dragsource CSS class so that IE has time to create a thumbnail before me make it opaque
                        _BaseUtils._yieldForDomModification(function () {
                            if (that._dragging) {
                                _ElementUtilities.addClass(that._itemBox, _Constants._dragSourceClass);
                            }
                        });
                    }
                },

                _onDragEnd: function ItemContainer_onDragEnd() {
                    this._dragging = false;
                    _ElementUtilities.removeClass(this._itemBox, _Constants._dragSourceClass);
                    this._itemEventsHandler.resetPointerDownState();
                },

                _onKeyDown: function ItemContainer_onKeyDown(eventObject) {
                    if (!this._itemEventsHandler._isInteractive(eventObject.target)) {
                        var Key = _ElementUtilities.Key,
                            keyCode = eventObject.keyCode,
                            swipeEnabled = this._swipeBehavior === _UI.SwipeBehavior.select;

                        var handled = false;
                        if (!eventObject.ctrlKey && keyCode === Key.enter) {
                            var allowed = this._verifySelectionAllowed();
                            if (allowed.canTapSelect) {
                                this.selected = !this.selected;
                            }
                            this._fireInvokeEvent();
                            handled = true;
                        } else if (eventObject.ctrlKey && keyCode === Key.enter ||
                            (swipeEnabled && eventObject.shiftKey && keyCode === Key.F10) ||
                            (swipeEnabled && keyCode === Key.menu) ||
                            keyCode === Key.space) {
                            if (!this.selectionDisabled) {
                                this.selected = !this.selected;
                                handled = _ElementUtilities._setActive(this.element);
                            }
                        } else if (keyCode === Key.escape && this.selected) {
                            this.selected = false;
                            handled = true;
                        }

                        if (handled) {
                            eventObject.stopPropagation();
                            eventObject.preventDefault();
                        }
                    }
                },

                _setTabIndex: function ItemContainer_setTabIndex() {
                    var currentTabIndex = this.element.getAttribute("tabindex");
                    if (!currentTabIndex) {
                        // Set the tabindex to 0 only if the application did not already
                        // provide a tabindex
                        this.element.setAttribute("tabindex", "0");
                    }
                },

                _rtl: function ItemContainer_rtl() {
                    if (typeof this._cachedRTL !== "boolean") {
                        this._cachedRTL = _Global.getComputedStyle(this.element, null).direction === "rtl";
                    }
                    return this._cachedRTL;
                },

                _setDirectionClass: function ItemContainer_setDirectionClass() {
                    _ElementUtilities[this._rtl() ? "addClass" : "removeClass"](this.element, _Constants._rtlListViewClass);
                },

                _forceLayout: function ItemContainer_forceLayout() {
                    this._cachedRTL = _Global.getComputedStyle(this.element, null).direction === "rtl";
                    this._setDirectionClass();
                },

                _getItemPosition: function ItemContainer_getItemPosition() {
                    var container = this.element;
                    if (container) {
                        return Promise.wrap({
                            left: (this._rtl() ?
                                container.offsetParent.offsetWidth - container.offsetLeft - container.offsetWidth :
                                container.offsetLeft),
                            top: container.offsetTop,
                            totalWidth: _ElementUtilities.getTotalWidth(container),
                            totalHeight: _ElementUtilities.getTotalHeight(container),
                            contentWidth: _ElementUtilities.getContentWidth(container),
                            contentHeight: _ElementUtilities.getContentHeight(container)
                        });
                    } else {
                        return Promise.cancel;
                    }
                },

                _itemPropertyChange: function ItemContainer_itemPropertyChange(list) {
                    if (this._disposed) { return; }

                    var container = list[0].target;
                    var ariaSelected = container.getAttribute("aria-selected") === "true";

                    // Only respond to aria-selected changes coming from UIA. This check
                    // relies on the fact that, in renderSelection, we update the selection
                    // visual before aria-selected.
                    if (ariaSelected !== _ElementUtilities._isSelectionRendered(this._itemBox)) {
                        if (this.selectionDisabled) {
                            // Revert the change made by UIA since the control has selection disabled
                            _ElementUtilities._setAttribute(container, "aria-selected", !ariaSelected);
                        } else {
                            this.selected = ariaSelected;
                            // Revert the change because the update was prevented on the selectionchanging event
                            if (ariaSelected !== this.selected) {
                                _ElementUtilities._setAttribute(container, "aria-selected", !ariaSelected);
                            }
                        }
                    }
                },

                _setSwipeClass: function ItemContainer_setSwipeClass() {
                    if (_BaseUtils.isPhone) {
                        // Cross-slide is disabled on phone
                        return;
                    }
                    // We apply an -ms-touch-action style to block panning and swiping from occurring at the same time.
                    if ((this._swipeBehavior === _UI.SwipeBehavior.select && this._selectionMode !== _UI.SelectionMode.none) || this._draggable) {
                        _ElementUtilities.addClass(this._element, _Constants._swipeableClass);
                    } else {
                        _ElementUtilities.removeClass(this._element, _Constants._swipeableClass);
                    }
                },

                _updateDraggableAttribute: function ItemContainer_updateDraggableAttribute() {
                    this._setSwipeClass();
                    this._itemBox.setAttribute("draggable", this._draggable);
                },

                _verifySelectionAllowed: function ItemContainer_verifySelectionAllowed() {
                    if (this._selectionMode !== _UI.SelectionMode.none && (this._tapBehavior === _UI.TapBehavior.toggleSelect || this._swipeBehavior === _UI.SwipeBehavior.select)) {
                        var canSelect = this._selection.fireSelectionChanging();
                        return {
                            canSelect: canSelect,
                            canTapSelect: canSelect && this._tapBehavior === _UI.TapBehavior.toggleSelect
                        };
                    } else {
                        return {
                            canSelect: false,
                            canTapSelect: false
                        };
                    }
                },

                _setupInternalTree: function ItemContainer_setupInternalTree() {
                    var item = _Global.document.createElement("div");
                    item.className = _Constants._itemClass;
                    this._captureProxy = _Global.document.createElement("div");
                    this._itemBox = _Global.document.createElement("div");
                    this._itemBox.className = _Constants._itemBoxClass;
                    var child = this.element.firstChild;
                    while (child) {
                        var sibling = child.nextSibling;
                        item.appendChild(child);
                        child = sibling;
                    }
                    this.element.appendChild(this._itemBox);
                    this._itemBox.appendChild(item);
                    this.element.appendChild(this._captureProxy);
                },

                _fireInvokeEvent: function ItemContainer_fireInvokeEvent() {
                    if (this.tapBehavior !== _UI.TapBehavior.none) {
                        var eventObject = _Global.document.createEvent("CustomEvent");
                        eventObject.initCustomEvent(eventNames.invoked, true, false, {});
                        this.element.dispatchEvent(eventObject);
                    }
                },

                _setAriaRole: function ItemContainer_setAriaRole() {
                    if (!this.element.getAttribute("role") || this._usingDefaultItemRole) {
                        this._usingDefaultItemRole = true;
                        var defaultItemRole;
                        if (this.tapBehavior === _UI.TapBehavior.none && this.selectionDisabled) {
                            defaultItemRole = "listitem";
                        } else {
                            defaultItemRole = "option";
                        }
                        _ElementUtilities._setAttribute(this.element, "role", defaultItemRole);
                    }
                },

                _writeProfilerMark: function ItemContainer_writeProfilerMark(text) {
                    var message = "WinJS.UI.ItemContainer:" + this._id + ":" + text;
                    _WriteProfilerMark(message);
                    _Log.log && _Log.log(message, null, "itemcontainerprofiler");
                }
            }, {
                // Names of classes used by the ItemContainer.
                _ClassName: {
                    itemContainer: "win-itemcontainer",
                    vertical: "win-vertical",
                    horizontal: "win-horizontal",
                }
            });
            _Base.Class.mix(ItemContainer, _Control.DOMEventMixin);
            return ItemContainer;
        }),

        _SingleItemSelectionManager: _Base.Namespace._lazy(function () {
            return _Base.Class.define(function SingleItemSelectionManager_ctor(element, itemBox) {
                this._selected = false;
                this._element = element;
                this._itemBox = itemBox;
            }, {
                selected: {
                    get: function () {
                        return this._selected;
                    },
                    set: function (value) {
                        value = !!value;
                        if (this._selected !== value) {
                            if (this.fireSelectionChanging()) {
                                this._selected = value;
                                _ItemEventsHandler._ItemEventsHandler.renderSelection(this._itemBox, this._element, value, true, this._element);
                                this.fireSelectionChanged();
                            }
                        }
                    }
                },

                count: function SingleItemSelectionManager_count() {
                    return this._selected ? 1 : 0;
                },

                getIndices: function SingleItemSelectionManager_getIndices() {
                    // not used
                },

                getItems: function SingleItemSelectionManager_getItems() {
                    // not used
                },

                getRanges: function SingleItemSelectionManager_getRanges() {
                    // not used
                },

                isEverything: function SingleItemSelectionManager_isEverything() {
                    return false;
                },

                set: function SingleItemSelectionManager_set() {
                    this.selected = true;
                },

                clear: function SingleItemSelectionManager_clear() {
                    this.selected = false;
                },

                add: function SingleItemSelectionManager_add() {
                    this.selected = true;
                },

                remove: function SingleItemSelectionManager_remove() {
                    this.selected = false;
                },

                selectAll: function SingleItemSelectionManager_selectAll() {
                    // not used
                },

                fireSelectionChanging: function SingleItemSelectionManager_fireSelectionChanging() {
                    var eventObject = _Global.document.createEvent("CustomEvent");
                    eventObject.initCustomEvent(eventNames.selectionchanging, true, true, {});
                    return this._element.dispatchEvent(eventObject);
                },

                fireSelectionChanged: function ItemContainer_fireSelectionChanged() {
                    var eventObject = _Global.document.createEvent("CustomEvent");
                    eventObject.initCustomEvent(eventNames.selectionchanged, true, false, {});
                    this._element.dispatchEvent(eventObject);
                },

                _isIncluded: function SingleItemSelectionManager_isIncluded() {
                    return this._selected;
                },

                _getFocused: function SingleItemSelectionManager_getFocused() {
                    return { type: _UI.ObjectType.item, index: _Constants._INVALID_INDEX };
                }
            });
        })
    });
});
// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/Repeater',[
    'exports',
    '../Core/_Global',
    '../Core/_Base',
    '../Core/_ErrorFromName',
    '../Core/_Events',
    '../Core/_Resources',
    '../Core/_WriteProfilerMark',
    '../BindingList',
    '../BindingTemplate',
    '../Promise',
    '../Utilities/_Control',
    '../Utilities/_Dispose',
    '../Utilities/_ElementUtilities',
    '../Utilities/_Hoverable',
    ], function repeaterInit(exports, _Global, _Base, _ErrorFromName, _Events, _Resources, _WriteProfilerMark, BindingList, BindingTemplate, Promise, _Control, _Dispose, _ElementUtilities, _Hoverable) {
    "use strict";

    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {
        /// <field>
        /// <summary locid="WinJS.UI.Repeater">
        /// Uses templates to generate HTML from a set of data.
        /// </summary>
        /// </field>
        /// <icon src="ui_winjs.ui.repeater.12x12.png" width="12" height="12" />
        /// <icon src="ui_winjs.ui.repeater.16x16.png" width="16" height="16" />
        /// <htmlSnippet><![CDATA[<div data-win-control="WinJS.UI.Repeater"></div>]]></htmlSnippet>
        /// <part name="repeater" class="win-repeater" locid="WinJS.UI.Repeater_part:repeater">The Repeater control itself</part>
        /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
        /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
        /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
        Repeater: _Base.Namespace._lazy(function () {

            // Constants
            var ITEMSLOADED = "itemsloaded",
                ITEMCHANGING = "itemchanging",
                ITEMCHANGED = "itemchanged",
                ITEMINSERTING = "iteminserting",
                ITEMINSERTED = "iteminserted",
                ITEMMOVING = "itemmoving",
                ITEMMOVED = "itemmoved",
                ITEMREMOVING = "itemremoving",
                ITEMREMOVED = "itemremoved",
                ITEMSRELOADING = "itemsreloading",
                ITEMSRELOADED = "itemsreloaded";

            var createEvent = _Events._createEventProperty;

            function stringifyItem(dataItem) {
                // Repeater uses this as its default renderer when no template is provided.
                var itemElement = _Global.document.createElement("div");
                itemElement.textContent = JSON.stringify(dataItem);
                return itemElement;
            }

            // Statics
            var strings = {
                get duplicateConstruction() { return "Invalid argument: Controls may only be instantiated one time for each DOM element"; },
                get asynchronousRender() { return "Top level items must render synchronously"; },
                get repeaterReentrancy() { return "Cannot modify Repeater data until Repeater has commited previous modification."; },
            };

            var Repeater = _Base.Class.define(function Repeater_ctor(element, options) {
                /// <signature helpKeyword="WinJS.UI.Repeater.Repeater">
                /// <summary locid="WinJS.UI.Repeater.constructor">
                /// Creates a new Repeater control.
                /// </summary>
                /// <param name="element" type="HTMLElement" domElement="true" isOptional="true" locid="WinJS.UI.Repeater.constructor_p:element">
                /// The DOM element that will host the new control. The Repeater will create an element if this value is null.
                /// </param>
                /// <param name="options" type="Object" isOptional="true" locid="WinJS.UI.Repeater.constructor_p:options">
                /// An object that contains one or more property/value pairs to apply to the
                /// new Repeater. Each property of the options object corresponds to one of the
                /// object's properties or events. Event names must begin with "on".
                /// </param>
                /// <returns type="WinJS.UI.Repeater" locid="WinJS.UI.Repeater.constructor_returnValue">
                /// The new Repeater control.
                /// </returns>
                /// </signature>

                // Check to make sure we weren't duplicated
                if (element && element.winControl) {
                    throw new _ErrorFromName("WinJS.UI.Repeater.DuplicateConstruction", strings.duplicateConstruction);
                }

                this._element = element || _Global.document.createElement("div");
                this._id = this._element.id || _ElementUtilities._uniqueID(this._element);
                this._writeProfilerMark("constructor,StartTM");
                options = options || {};
                _ElementUtilities.addClass(this._element, "win-repeater win-disposable");

                this._render = null;
                this._modifying = false;
                this._disposed = false;
                this._element.winControl = this;
                this._dataListeners = {
                    itemchanged: this._dataItemChangedHandler.bind(this),
                    iteminserted: this._dataItemInsertedHandler.bind(this),
                    itemmoved: this._dataItemMovedHandler.bind(this),
                    itemremoved: this._dataItemRemovedHandler.bind(this),
                    reload: this._dataReloadHandler.bind(this),
                };

                // Consume Repeater innerHTML and return a template.
                var inlineTemplate = this._extractInlineTemplate();
                this._initializing = true;
                // Use the inlinetemplate if a parameter was not given.
                // Either way, Repeater's innerHTML has now been consumed.
                this.template = options.template || inlineTemplate;

                this.data = options.data;
                this._initializing = false;

                _Control._setOptions(this, options, true); // Events only

                this._repeatedDOM = [];
                this._renderAllItems();
                this.dispatchEvent(ITEMSLOADED, {});

                this._writeProfilerMark("constructor,StopTM");
            }, {

                /// <field type="HTMLElement" domElement="true" readonly="true" hidden="true" locid="WinJS.UI.Repeater.element" helpKeyword="WinJS.UI.Repeater.element">
                /// Gets the DOM element that hosts the Repeater.
                /// </field>
                element: {
                    get: function () {
                        return this._element;
                    }
                },

                /// <field type="WinJS.Binding.List" locid="WinJS.UI.Repeater.data" helpKeyword="WinJS.UI.Repeater.data">
                /// Gets or sets the WinJS.Binding.List that provides the Repeater control with items to display.
                /// </field>
                data: {
                    get: function () { return this._data; },
                    set: function (data) {
                        this._writeProfilerMark("data.set,StartTM");
                        if (this._data) {
                            this._removeDataListeners();
                        }
                        this._data = data || new BindingList.List();
                        this._addDataListeners();
                        if (!this._initializing) {
                            this._reloadRepeater(true);
                            this.dispatchEvent(ITEMSLOADED, {});
                        }
                        this._writeProfilerMark("data.set,StopTM");
                    }
                },

                /// <field type="Object" locid="WinJS.UI.Repeater.template" helpKeyword="WinJS.UI.Repeater.template" potentialValueSelector="[data-win-control='WinJS.Binding.Template']">
                /// Gets or sets a Template or custom rendering function that defines the HTML of each item within the Repeater.
                /// </field>
                template: {
                    get: function () { return this._template; },
                    set: function (template) {
                        this._writeProfilerMark("template.set,StartTM");
                        this._template = (template || stringifyItem);
                        this._render = _ElementUtilities._syncRenderer(this._template, this.element.tagName);
                        if (!this._initializing) {
                            this._reloadRepeater(true);
                            this.dispatchEvent(ITEMSLOADED, {});
                        }
                        this._writeProfilerMark("template.set,StopTM");
                    }
                },

                /// <field type="Number" hidden="true" locid="WinJS.UI.Repeater.length" helpKeyword="WinJS.UI.Repeater.length">
                /// Gets the number of items in the Repeater control.
                /// </field>
                length: {
                    get: function () { return this._repeatedDOM.length; },
                },

                elementFromIndex: function Repeater_elementFromIndex(index) {
                    /// <signature helpKeyword="WinJS.UI.Repeater.elementFromIndex">
                    /// <summary locid="WinJS.UI.Repeater.elementFromIndex">
                    /// Returns the HTML element for the item with the specified index.
                    /// </summary>
                    /// <param name="index" type="Number" locid="WinJS.UI.Repeater.elementFromIndex _p:index">
                    /// The index of the item.
                    /// </param>
                    /// <returns type="HTMLElement" domElement="true" locid=" WinJS.UI.Repeater.elementFromIndex_returnValue">
                    /// The DOM element for the specified item.
                    /// </returns>
                    /// </signature>
                    return this._repeatedDOM[index];
                },

                dispose: function Repeater_dispose() {
                    /// <signature helpKeyword="WinJS.UI.Repeater.dispose">
                    /// <summary locid="WinJS.UI.Repeater.dispose">
                    /// Prepare this Repeater for garbage collection.
                    /// </summary>
                    /// </signature>
                    if (this._disposed) {
                        return;
                    }
                    this._disposed = true; // Mark this control as disposed.
                    this._removeDataListeners();
                    this._data = null;
                    this._template = null;
                    for (var i = 0, len = this._repeatedDOM.length; i < len; i++) {
                        _Dispose._disposeElement(this._repeatedDOM[i]);
                    }
                },

                /// <field type="Function" locid="WinJS.UI.Repeater.onitemsloaded" helpKeyword="WinJS.UI.Repeater.onitemsloaded">
                /// Raised when the Repeater has finished loading a new set of data. This event is only fired on construction
                /// or when the Repeater control's data source or template is replaced.
                /// </field>
                onitemsloaded: createEvent(ITEMSLOADED),

                /// <field type="Function" locid="WinJS.UI.Repeater.onitemchanging" helpKeyword="WinJS.UI.Repeater.onitemchanging">
                /// Raised after an item in the Repeater control's data source changes but before the corresponding DOM element has been updated.
                /// </field>
                onitemchanging: createEvent(ITEMCHANGING),

                /// <field type="Function" locid="WinJS.UI.Repeater.onitemchanged" helpKeyword="WinJS.UI.Repeater.onitemchanged">
                /// Raised after an item in the Repeater control's data source changes and after the corresponding DOM element has been updated.
                /// </field>
                onitemchanged: createEvent(ITEMCHANGED),

                /// <field type="Function" locid="WinJS.UI.Repeater.oniteminserting" helpKeyword="WinJS.UI.Repeater.oniteminserting">
                /// Raised after an item has been added to the Repeater control's data source but before the corresponding DOM element has been added.
                /// </field>
                oniteminserting: createEvent(ITEMINSERTING),

                /// <field type="Function" locid="WinJS.UI.Repeater.oniteminserted" helpKeyword="WinJS.UI.Repeater.oniteminserted">
                /// Raised after an item has been added to the Repeater control's data source and after the corresponding DOM element has been added.
                /// </field>
                oniteminserted: createEvent(ITEMINSERTED),

                /// <field type="Function" locid="WinJS.UI.Repeater.onitemmoving" helpKeyword="WinJS.UI.Repeater.onitemmoving">
                /// Raised after an item has been moved from one index to another in the Repeater control's data source but before the corresponding DOM element has been moved.
                /// </field>
                onitemmoving: createEvent(ITEMMOVING),

                /// <field type="Function" locid="WinJS.UI.Repeater.onitemmoved" helpKeyword="WinJS.UI.Repeater.onitemmoved">
                /// Raised after an item has been moved from one index to another in the Repeater control's data source and after the corresponding DOM element has been moved.
                /// </field>
                onitemmoved: createEvent(ITEMMOVED),

                /// <field type="Function" locid="WinJS.UI.Repeater.onitemremoving" helpKeyword="WinJS.UI.Repeater.onitemremoving">
                /// Raised after an item has been removed from the Repeater control's data source but before the corresponding DOM element has been removed.
                /// </field>
                onitemremoving: createEvent(ITEMREMOVING),

                /// <field type="Function" locid="WinJS.UI.Repeater.onitemremoved" helpKeyword="WinJS.UI.Repeater.onitemremoved">
                /// Raised after an item has been removed from one index to another in the Repeater control's data source and after the corresponding DOM element has been removed.
                /// </field>
                onitemremoved: createEvent(ITEMREMOVED),

                /// <field type="Function" locid="WinJS.UI.Repeater.onitemsreloading" helpKeyword="WinJS.UI.Repeater.onitemsreloading">
                /// The list has been refreshed and any references to data in the list may be incorrect.
                /// Raised after the Repeater control's underlying data has been updated but before the updated HTML has been reloaded.
                /// </field>
                onitemsreloading: createEvent(ITEMSRELOADING),

                /// <field type="Function" locid="WinJS.UI.Repeater.onitemsreloaded" helpKeyword="WinJS.UI.Repeater.onitemsreloaded">
                /// Raised after the Repeater control's underlying data has been updated and after the updated HTML has been reloaded.
                /// </field>
                onitemsreloaded: createEvent(ITEMSRELOADED),

                _extractInlineTemplate: function Repeater_extractInlineTemplate() {
                    // Creates and returns a WinJS.BindingTemplate from the Repeater innerHTML.
                    if (this._element.firstElementChild) {
                        var templateElement = _Global.document.createElement(this._element.tagName);
                        while (this._element.firstElementChild) {
                            // Move each child element from the Repeater to the Template Element
                            templateElement.appendChild(this._element.firstElementChild);
                        }
                        return new BindingTemplate.Template(templateElement, { extractChild: true });
                    }
                },

                _renderAllItems: function Repeater_renderAllItems() {
                    var fragment = _Global.document.createDocumentFragment();
                    for (var i = 0, len = this._data.length; i < len; i++) {
                        var renderedItem = this._render(this._data.getAt(i));
                        if (!renderedItem) {
                            throw new _ErrorFromName("WinJS.UI.Repeater.AsynchronousRender", strings.asynchronousRender);

                        }
                        fragment.appendChild(renderedItem);
                        this._repeatedDOM.push(renderedItem);
                    }
                    this._element.appendChild(fragment);
                },

                _reloadRepeater: function Repeater_reloadRepeater(shouldDisposeElements) {
                    this._unloadRepeatedDOM(shouldDisposeElements);
                    this._repeatedDOM = [];
                    this._renderAllItems();
                },

                _unloadRepeatedDOM: function Repeater_unloadRepeatedDOM(shouldDisposeElements) {
                    for (var i = 0, len = this._repeatedDOM.length; i < len; i++) {
                        var element = this._repeatedDOM[i];
                        if (!!shouldDisposeElements) {
                            // this_dataReloadHandler uses this to defer disposal until after animations have completed,
                            // at which point it manually disposes each element.
                            _Dispose._disposeElement(element);
                        }
                        if (element.parentElement === this._element) {
                            this._element.removeChild(element);
                        }
                    }
                },

                _addDataListeners: function Repeater_addDataListeners() {
                    Object.keys(this._dataListeners).forEach(function (eventName) {
                        this._data.addEventListener(eventName, this._dataListeners[eventName], false);
                    }.bind(this));
                },

                _beginModification: function Repeater_beginModification() {
                    if (this._modifying) {
                        throw new _ErrorFromName("WinJS.UI.Repeater.RepeaterModificationReentrancy", strings.repeaterReentrancy);
                    }
                    this._modifying = true;
                },

                _endModification: function Repeater_endModification() {
                    this._modifying = false;
                },

                _removeDataListeners: function Repeater_removeDataListeners() {
                    Object.keys(this._dataListeners).forEach(function (eventName) {
                        this._data.removeEventListener(eventName, this._dataListeners[eventName], false);
                    }.bind(this));
                },

                _dataItemChangedHandler: function Repeater_dataItemChangedHandler(eventInfo) {
                    // Handles the 'itemchanged' event fired by WinJS.Binding.List

                    this._beginModification();
                    var animationPromise;

                    var root = this._element;
                    var index = eventInfo.detail.index;
                    var renderedItem = this._render(eventInfo.detail.newValue);
                    if (!renderedItem) {
                        throw new _ErrorFromName("WinJS.UI.Repeater.AsynchronousRender", strings.asynchronousRender);
                    }

                    // Append to the event object
                    if (this._repeatedDOM[index]) {
                        eventInfo.detail.oldElement = this._repeatedDOM[index];
                    }
                    eventInfo.detail.newElement = renderedItem;
                    eventInfo.detail.setPromise = function setPromise(delayPromise) {
                        animationPromise = delayPromise;
                    };

                    this._writeProfilerMark(ITEMCHANGING + ",info");
                    this.dispatchEvent(ITEMCHANGING, eventInfo.detail);

                    // Make the change
                    var oldItem = null;
                    if (index < this._repeatedDOM.length) {
                        oldItem = this._repeatedDOM[index];
                        root.replaceChild(renderedItem, oldItem);
                        this._repeatedDOM[index] = renderedItem;
                    } else {
                        root.appendChild(renderedItem);
                        this._repeatedDOM.push(renderedItem);
                    }

                    this._endModification();
                    this._writeProfilerMark(ITEMCHANGED + ",info");
                    this.dispatchEvent(ITEMCHANGED, eventInfo.detail);

                    if (oldItem) { // Give the option to delay element disposal.
                        Promise.as(animationPromise).done(function () {
                            _Dispose._disposeElement(oldItem);
                        }.bind(this));
                    }
                },

                _dataItemInsertedHandler: function Repeater_dataItemInsertedHandler(eventInfo) {
                    // Handles the 'iteminserted' event fired by WinJS.Binding.List

                    this._beginModification();
                    var index = eventInfo.detail.index;
                    var renderedItem = this._render(eventInfo.detail.value);
                    if (!renderedItem) {
                        throw new _ErrorFromName("WinJS.UI.Repeater.AsynchronousRender", strings.asynchronousRender);
                    }

                    var root = this._element;

                    eventInfo.detail.affectedElement = renderedItem;
                    this._writeProfilerMark(ITEMINSERTING + ",info");
                    this.dispatchEvent(ITEMINSERTING, eventInfo.detail);

                    if (index < this._repeatedDOM.length) {
                        var nextSibling = this._repeatedDOM[index];
                        root.insertBefore(renderedItem, nextSibling);
                    } else {
                        root.appendChild(renderedItem);
                    }

                    // Update collection of rendered elements
                    this._repeatedDOM.splice(index, 0, renderedItem);

                    this._endModification();
                    this._writeProfilerMark(ITEMINSERTED + ",info");
                    this.dispatchEvent(ITEMINSERTED, eventInfo.detail);

                },

                _dataItemMovedHandler: function Repeater_dataItemMovedHandler(eventInfo) {
                    // Handles the 'itemmoved' event fired by WinJS.Binding.List

                    this._beginModification();

                    var movingItem = this._repeatedDOM[eventInfo.detail.oldIndex];

                    // Fire the event before we start the move.
                    eventInfo.detail.affectedElement = movingItem;
                    this._writeProfilerMark(ITEMMOVING + ",info");
                    this.dispatchEvent(ITEMMOVING, eventInfo.detail);

                    // Remove
                    this._repeatedDOM.splice(eventInfo.detail.oldIndex, 1)[0];
                    movingItem.parentNode.removeChild(movingItem);

                    // Insert
                    if (eventInfo.detail.newIndex < (this._data.length) - 1) {
                        var nextSibling = this._repeatedDOM[eventInfo.detail.newIndex];
                        this._element.insertBefore(movingItem, nextSibling);
                        this._repeatedDOM.splice(eventInfo.detail.newIndex, 0, movingItem);
                    } else {
                        this._repeatedDOM.push(movingItem);
                        this._element.appendChild(movingItem);
                    }

                    this._endModification();
                    this._writeProfilerMark(ITEMMOVED + ",info");
                    this.dispatchEvent(ITEMMOVED, eventInfo.detail);
                },

                _dataItemRemovedHandler: function Repeater_dataItemRemoveHandler(eventInfo) {
                    // Handles the 'itemremoved' event fired by WinJS.Binding.List

                    this._beginModification();
                    var animationPromise;
                    var oldItem = this._repeatedDOM[eventInfo.detail.index];

                    // Trim 'value' and 'key' from the eventInfo.details that Binding.List gave for the removal case,
                    // since both of those properties already exist inside of eventInfo.details.item.
                    var eventDetail = { affectedElement: oldItem, index: eventInfo.detail.index, item: eventInfo.detail.item };
                    eventDetail.setPromise = function setPromise(delayPromise) {
                        animationPromise = delayPromise;
                    };

                    this._writeProfilerMark(ITEMREMOVING + ",info");
                    this.dispatchEvent(ITEMREMOVING, eventDetail);

                    oldItem.parentNode.removeChild(oldItem);
                    this._repeatedDOM.splice(eventInfo.detail.index, 1);

                    this._endModification();
                    this._writeProfilerMark(ITEMREMOVED + ",info");
                    this.dispatchEvent(ITEMREMOVED, eventDetail);

                    Promise.as(animationPromise).done(function () {
                        _Dispose._disposeElement(oldItem);
                    }.bind(this));
                },

                _dataReloadHandler: function Repeater_dataReloadHandler() {
                    // Handles the 'reload' event fired by WinJS.Binding.List whenever it performs operations such as reverse() or sort()

                    this._beginModification();
                    var animationPromise;

                    var shallowCopyBefore = this._repeatedDOM.slice(0);
                    var eventDetail = { affectedElements: shallowCopyBefore };
                    eventDetail.setPromise = function (delayPromise) {
                        animationPromise = delayPromise;
                    };

                    this._writeProfilerMark(ITEMSRELOADING + ",info");
                    this.dispatchEvent(ITEMSRELOADING, eventDetail);
                    this._reloadRepeater(false /*shouldDisposeElements */);

                    var shallowCopyAfter = this._repeatedDOM.slice(0);
                    this._endModification();
                    this._writeProfilerMark(ITEMSRELOADED + ",info");
                    this.dispatchEvent(ITEMSRELOADED, { affectedElements: shallowCopyAfter });

                    Promise.as(animationPromise).done(function () { // Gives the option to defer disposal.
                        for (var i = 0, len = shallowCopyBefore.length; i < len; i++) {
                            _Dispose._disposeElement(shallowCopyBefore[i]);
                        }
                    }.bind(this));
                },

                _writeProfilerMark: function Repeater_writeProfilerMark(text) {
                    _WriteProfilerMark("WinJS.UI.Repeater:" + this._id + ":" + text);
                }
            }, {
                isDeclarativeControlContainer: true,
            });
            _Base.Class.mix(Repeater, _Control.DOMEventMixin);
            return Repeater;
        })
    });

});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/DatePicker',[
    '../Core/_Global',
    '../Core/_WinRT',
    '../Core/_Base',
    '../Core/_BaseUtils',
    '../Core/_Events',
    '../Core/_Resources',
    '../Utilities/_Control',
    '../Utilities/_ElementUtilities',
    '../Utilities/_Hoverable',
    '../Utilities/_Select',
    'require-style!less/desktop/controls',
    'require-style!less/phone/controls'
    ], function datePickerInit(_Global, _WinRT, _Base, _BaseUtils, _Events, _Resources, _Control, _ElementUtilities, _Hoverable, _Select) {
    "use strict";

    _Base.Namespace.define("WinJS.UI", {
        /// <field>
        /// <summary locid="WinJS.UI.DatePicker">Allows users to pick a date value.</summary>
        /// <compatibleWith platform="Windows" minVersion="8.0"/>
        /// </field>
        /// <name locid="WinJS.UI.DatePicker_name">Date Picker</name>
        /// <icon src="ui_winjs.ui.datepicker.12x12.png" width="12" height="12" />
        /// <icon src="ui_winjs.ui.datepicker.16x16.png" width="16" height="16" />
        /// <htmlSnippet><![CDATA[<div data-win-control="WinJS.UI.DatePicker"></div>]]></htmlSnippet>
        /// <event name="change" locid="WinJS.UI.DatePicker_e:change">Occurs when the current date changes.</event>
        /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
        /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
        /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
        DatePicker: _Base.Namespace._lazy(function () {
            // Constants definition
            var DEFAULT_DAY_PATTERN = 'day',
                DEFAULT_MONTH_PATTERN = '{month.full}',
                DEFAULT_YEAR_PATTERN = 'year.full';

            var strings = {
                get ariaLabel() { return _Resources._getWinJSString("ui/datePicker").value; },
                get selectDay() { return _Resources._getWinJSString("ui/selectDay").value; },
                get selectMonth() { return _Resources._getWinJSString("ui/selectMonth").value; },
                get selectYear() { return _Resources._getWinJSString("ui/selectYear").value; },
            };

            var yearFormatCache = {};

            function newFormatter(pattern, calendar, defaultPattern) {
                var dtf = _WinRT.Windows.Globalization.DateTimeFormatting;
                pattern = !pattern ? defaultPattern : pattern;
                var c = new dtf.DateTimeFormatter(pattern);
                if (calendar) {
                    return new dtf.DateTimeFormatter(pattern, c.languages, c.geographicRegion, calendar, c.clock);
                }
                return c;
            }

            function formatCacheLookup(pattern, calendar, defaultPattern) {
                var pat = yearFormatCache[pattern];
                if (!pat) {
                    pat = yearFormatCache[pattern] = {};
                }
                var cal = pat[calendar];
                if (!cal) {
                    cal = pat[calendar] = {};
                }
                var def = cal[defaultPattern];
                if (!def) {
                    def = cal[defaultPattern] = {};
                    def.formatter = newFormatter(pattern, calendar, defaultPattern);
                    def.years = {};
                }
                return def;
            }

            function formatYear(pattern, calendar, defaultPattern, datePatterns, order, cal) {
                var cache = formatCacheLookup(pattern, calendar, defaultPattern);
                var y = cache.years[cal.year + "-" + cal.era];
                if (!y) {
                    y = cache.formatter.format(cal.getDateTime());
                    cache.years[cal.year + "-" + cal.era] = y;
                }
                return y;
            }

            function formatMonth(pattern, calendar, defaultPattern, cal) {
                var cache = formatCacheLookup(pattern, calendar, defaultPattern);
                // can't cache actual month names because the hebrew calendar varies
                // the month name depending on religious holidays and leap months.
                //
                return cache.formatter.format(cal.getDateTime());
            }

            function formatDay(pattern, calendar, defaultPattern, cal) {
                var cache = formatCacheLookup(pattern, calendar, defaultPattern);
                // can't cache actual day names because the format may include the day of the week,
                // which, of course, varies from month to month.
                //
                return cache.formatter.format(cal.getDateTime());
            }

            function newCal(calendar) {
                var glob = _WinRT.Windows.Globalization;
                var c = new glob.Calendar();
                if (calendar) {
                    return new glob.Calendar(c.languages, calendar, c.getClock());
                }
                return c;
            }

            function yearDiff(start, end) {
                var yearCount = 0;

                if (start.era === end.era) {
                    yearCount = end.year - start.year;
                } else {
                    while (start.era !== end.era || start.year !== end.year) {
                        yearCount++;
                        start.addYears(1);
                    }
                }
                return yearCount;
            }

            var DatePicker = _Base.Class.define(function DatePicker_ctor(element, options) {
                /// <signature helpKeyword="WinJS.UI.DatePicker.DatePicker">
                /// <summary locid="WinJS.UI.DatePicker.constructor">Creates a new DatePicker control.</summary>
                /// <param name="element" type="HTMLElement" domElement="true" locid="WinJS.UI.DatePicker.constructor_p:element">
                /// The DOM element that will host the DatePicker control.
                /// </param>
                /// <param name="options" type="Object" locid="WinJS.UI.DatePicker.constructor_p:options">
                /// An object that contains one or more property/value pairs to apply to the new control. Each property of the options object corresponds
                /// to one of the control's properties or events.
                /// </param>
                /// <returns type="WinJS.UI.DatePicker" locid="WinJS.UI.DatePicker.constructor_returnValue">A constructed DatePicker control.</returns>
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </signature>

                // Default to current date
                this._currentDate = new Date();

                // Default to +/- 100 years
                this._minYear = this._currentDate.getFullYear() - 100;
                this._maxYear = this._currentDate.getFullYear() + 100;
                this._datePatterns = {
                    date: null,
                    month: null,
                    year: null
                };

                element = element || _Global.document.createElement("div");
                _ElementUtilities.addClass(element, "win-disposable");
                element.winControl = this;

                var label = element.getAttribute("aria-label");
                if (!label) {
                    element.setAttribute("aria-label", strings.ariaLabel);
                }

                // Options should be set after the element is initialized which is
                // the same order of operation as imperatively setting options.
                this._init(element);
                _Control.setOptions(this, options);
            }, {
                _information: null,
                _currentDate: null,
                _calendar: null,
                _disabled: false,
                _dateElement: null,
                _dateControl: null,
                _monthElement: null,
                _monthControl: null,
                _minYear: null,
                _maxYear: null,
                _yearElement: null,
                _yearControl: null,
                _datePatterns: {
                    date: null,
                    month: null,
                    year: null
                },

                _addAccessibilityAttributes: function () {
                    //see http://www.w3.org/TR/wai-aria/rdf_model.png for details
                    this._domElement.setAttribute("role", "group");

                    this._dateElement.setAttribute("aria-label", strings.selectDay);
                    this._monthElement.setAttribute("aria-label", strings.selectMonth);
                    this._yearElement.setAttribute("aria-label", strings.selectYear);
                },

                _addControlsInOrder: function () {
                    var e = this._domElement;
                    var that = this;
                    var orderIndex = 0; // don't use forEach's index, because "era" is in the list
                    that._information.order.forEach(function (s) {
                        switch (s) {
                            case "month":
                                e.appendChild(that._monthElement);
                                _ElementUtilities.addClass(that._monthElement, "win-order" + (orderIndex++));
                                break;
                            case "date":
                                e.appendChild(that._dateElement);
                                _ElementUtilities.addClass(that._dateElement, "win-order" + (orderIndex++));
                                break;
                            case "year":
                                e.appendChild(that._yearElement);
                                _ElementUtilities.addClass(that._yearElement, "win-order" + (orderIndex++));
                                break;
                        }
                    });
                },

                _createControlElements: function () {
                    this._monthElement = _Global.document.createElement("select");
                    this._monthElement.className = "win-datepicker-month";
                    this._dateElement = _Global.document.createElement("select");
                    this._dateElement.className = "win-datepicker-date";
                    this._yearElement = _Global.document.createElement("select");
                    this._yearElement.className = "win-datepicker-year";
                },

                _createControls: function () {
                    var info = this._information;
                    var index = info.getIndex(this.current);

                    if (info.forceLanguage) {
                        this._domElement.setAttribute("lang", info.forceLanguage);
                        this._domElement.setAttribute("dir", info.isRTL ? "rtl" : "ltr");
                    }


                    this._yearControl = new _Select._Select(this._yearElement, {
                        dataSource: this._information.years,
                        disabled: this.disabled,
                        index: index.year
                    });

                    this._monthControl = new _Select._Select(this._monthElement, {
                        dataSource: this._information.months(index.year),
                        disabled: this.disabled,
                        index: index.month
                    });

                    this._dateControl = new _Select._Select(this._dateElement, {
                        dataSource: this._information.dates(index.year, index.month),
                        disabled: this.disabled,
                        index: index.date
                    });

                    this._wireupEvents();
                },
                dispose: function () {
                    /// <signature helpKeyword="WinJS.UI.DatePicker.dispose">
                    /// <summary locid="WinJS.UI.DatePicker.dispose">
                    /// Disposes this control.
                    /// </summary>
                    /// <compatibleWith platform="Windows" minVersion="8.0"/>
                    /// </signature>
                },

                /// <field type="String" locid="WinJS.UI.DatePicker.calendar" helpKeyword="WinJS.UI.DatePicker.calendar">
                /// Gets or sets the calendar to use.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                calendar: {
                    get: function () {
                        return this._calendar;
                    },
                    set: function (value) {
                        this._calendar = value;
                        this._setElement(this._domElement);
                    }
                },

                /// <field type="Date" locid="WinJS.UI.DatePicker.current" helpKeyword="WinJS.UI.DatePicker.current">
                /// Gets or sets the current date of the DatePicker.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                current: {
                    get: function () {
                        var d = this._currentDate;
                        var y = d.getFullYear();
                        return new Date(Math.max(Math.min(this.maxYear, y), this.minYear), d.getMonth(), d.getDate(), 12, 0, 0, 0);
                    },
                    set: function (value) {
                        var newDate;
                        if (typeof (value) === "string") {
                            newDate = new Date(Date.parse(value));
                            newDate.setHours(12, 0, 0, 0);
                        } else {
                            newDate = value;
                        }

                        var oldDate = this._currentDate;
                        if (oldDate !== newDate) {
                            this._currentDate = newDate;
                            this._updateDisplay();
                        }
                    }
                },

                /// <field type="Boolean" locid="WinJS.UI.DatePicker.disabled" helpKeyword="WinJS.UI.DatePicker.disabled">
                /// Gets or sets a value that specifies whether the DatePicker is disabled. A value of true indicates that the DatePicker is disabled.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                disabled: {
                    get: function () { return this._disabled; },
                    set: function (value) {
                        if (this._disabled !== value) {
                            this._disabled = value;
                            // all controls get populated at the same time, so any check is OK
                            //
                            if (this._yearControl) {
                                this._monthControl.setDisabled(value);
                                this._dateControl.setDisabled(value);
                                this._yearControl.setDisabled(value);
                            }
                        }
                    }
                },

                /// <field type="String" locid="WinJS.UI.DatePicker.datePattern" helpKeyword="WinJS.UI.DatePicker.datePattern">
                /// Gets or sets the display pattern for the date.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                datePattern: {
                    get: function () { return this._datePatterns.date; },
                    set: function (value) {
                        if (this._datePatterns.date !== value) {
                            this._datePatterns.date = value;
                            this._init();
                        }
                    }
                },


                /// <field type="HTMLElement" domElement="true" hidden="true" locid="WinJS.UI.DatePicker.element" helpKeyword="WinJS.UI.DatePicker.element">
                /// Gets the DOM element for the DatePicker.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                element: {
                    get: function () { return this._domElement; }
                },

                _setElement: function (element) {
                    this._domElement = this._domElement || element;
                    if (!this._domElement) { return; }

                    _ElementUtilities.empty(this._domElement);
                    _ElementUtilities.addClass(this._domElement, "win-datepicker");

                    this._updateInformation();

                    this._createControlElements();
                    this._addControlsInOrder();
                    this._createControls();
                    this._addAccessibilityAttributes();
                },

                /// <field type="Number" integer="true" locid="WinJS.UI.DatePicker.minYear" helpKeyword="WinJS.UI.DatePicker.minYear">
                /// Gets or sets the minimum Gregorian year available for picking.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                minYear: {
                    get: function () {
                        return this._information.getDate({ year: 0, month: 0, date: 0 }).getFullYear();
                    },
                    set: function (value) {
                        if (this._minYear !== value) {
                            this._minYear = value;
                            if (value > this._maxYear) {
                                this._maxYear = value;
                            }
                            this._updateInformation();
                            if (this._yearControl) {
                                this._yearControl.dataSource = this._information.years;
                            }

                            this._updateDisplay();
                        }
                    }
                },

                /// <field type="Number" integer="true" locid="WinJS.UI.DatePicker.maxYear" helpKeyword="WinJS.UI.DatePicker.maxYear">
                /// Gets or sets the maximum Gregorian year available for picking.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                maxYear: {
                    get: function () {
                        var index = {
                            year: this._information.years.getLength() - 1
                        };
                        index.month = this._information.months(index.year).getLength() - 1;
                        index.date = this._information.dates(index.year, index.month).getLength() - 1;
                        return this._information.getDate(index).getFullYear();
                    },
                    set: function (value) {
                        if (this._maxYear !== value) {
                            this._maxYear = value;
                            if (value < this._minYear) {
                                this._minYear = value;
                            }
                            this._updateInformation();
                            if (this._yearControl) {
                                this._yearControl.dataSource = this._information.years;
                            }

                            this._updateDisplay();
                        }
                    }
                },

                /// <field type="String" locid="WinJS.UI.DatePicker.monthPattern" helpKeyword="WinJS.UI.DatePicker.monthPattern">
                /// Gets or sets the display pattern for the month.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                monthPattern: {
                    get: function () { return this._datePatterns.month; },
                    set: function (value) {
                        if (this._datePatterns.month !== value) {
                            this._datePatterns.month = value;
                            this._init();
                        }
                    }
                },

                _updateInformation: function () {
                    // since "year" in the date ctor can be two digit (85 == 1985), we need
                    // to force "full year" to capture dates < 100 a.d.
                    //
                    var min = new Date(this._minYear, 0, 1, 12, 0, 0);
                    var max = new Date(this._maxYear, 11, 31, 12, 0, 0);
                    min.setFullYear(this._minYear);
                    max.setFullYear(this._maxYear);

                    this._information = DatePicker.getInformation(min, max, this._calendar, this._datePatterns);
                },

                _init: function (element) {
                    this._setElement(element);
                },

                _updateDisplay: function () {
                    if (!this._domElement) {
                        return;
                    }

                    // all controls get populated at the same time, so any check is OK
                    //
                    if (this._yearControl) {
                        //Render display index based on constraints (minYear and maxYear constraints)
                        //Will not modify current date
                        var index = this._information.getIndex(this.current);

                        this._yearControl.index = index.year;
                        this._monthControl.dataSource = this._information.months(index.year);
                        this._monthControl.index = index.month;
                        this._dateControl.dataSource = this._information.dates(index.year, index.month);
                        this._dateControl.index = index.date;
                    }
                },

                _wireupEvents: function () {
                    var that = this;
                    function changed() {
                        that._currentDate = that._information.getDate({ year: that._yearControl.index, month: that._monthControl.index, date: that._dateControl.index }, that._currentDate);
                        var index = that._information.getIndex(that._currentDate);

                        // Changing the month (or year, if the current date is 2/29) changes the day range, and could have made the day selection invalid
                        that._monthControl.dataSource = that._information.months(index.year);
                        that._monthControl.index = index.month;
                        that._dateControl.dataSource = that._information.dates(index.year, index.month);
                        that._dateControl.index = index.date;
                    }

                    this._dateElement.addEventListener("change", changed, false);
                    this._monthElement.addEventListener("change", changed, false);
                    this._yearElement.addEventListener("change", changed, false);
                },

                /// <field type="String" locid="WinJS.UI.DatePicker.yearPattern" helpKeyword="WinJS.UI.DatePicker.yearPattern">
                /// Gets or sets the display pattern for year.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                yearPattern: {
                    get: function () { return this._datePatterns.year; },
                    set: function (value) {
                        if (this._datePatterns.year !== value) {
                            this._datePatterns.year = value;
                            this._init();
                        }
                    }
                },
            }, {
                _getInformationWinRT: function (startDate, endDate, calendar, datePatterns) {
                    datePatterns = datePatterns || { date: DEFAULT_DAY_PATTERN, month: DEFAULT_MONTH_PATTERN, year: DEFAULT_YEAR_PATTERN };

                    var tempCal = newCal(calendar);
                    var monthCal = newCal(calendar);
                    var dayCal = newCal(calendar);

                    tempCal.setToMin();
                    var minDateTime = tempCal.getDateTime();
                    tempCal.setToMax();
                    var maxDateTime = tempCal.getDateTime();

                    function clamp(date) {
                        return new Date(Math.min(new Date(Math.max(minDateTime, date)), maxDateTime));
                    }

                    tempCal.hour = 12;

                    startDate = clamp(startDate);
                    endDate = clamp(endDate);

                    tempCal.setDateTime(endDate);
                    var end = { year: tempCal.year, era: tempCal.era };

                    tempCal.setDateTime(startDate);
                    var yearLen = 0;

                    yearLen = yearDiff(tempCal, end) + 1;

                    // Explicity use a template that's equivalent to a longdate template
                    // as longdate/shortdate can be overriden by the user
                    var dateformat = formatCacheLookup("day month.full year", calendar).formatter;
                    var localdatepattern = dateformat.patterns[0];
                    var isRTL = localdatepattern.charCodeAt(0) === 8207;
                    var order = ["date", "month", "year"];

                    var indexes = {
                        month: localdatepattern.indexOf("{month"),
                        date: localdatepattern.indexOf("{day"),
                        year: localdatepattern.indexOf("{year")
                    };
                    order.sort(function (a, b) {
                        if (indexes[a] < indexes[b]) {
                            return -1;
                        } else if (indexes[a] > indexes[b]) {
                            return 1;
                        } else {
                            return 0;
                        }
                    });

                    var yearSource = (function () {
                        return {
                            getLength: function () { return yearLen; },
                            getValue: function (index) {
                                tempCal.setDateTime(startDate);
                                tempCal.addYears(index);

                                return formatYear(datePatterns.year, calendar, DEFAULT_YEAR_PATTERN, datePatterns, order, tempCal);
                            }
                        };
                    })();

                    var monthSource = function (yearIndex) {
                        monthCal.setDateTime(startDate);
                        monthCal.addYears(yearIndex);

                        return {
                            getLength: function () { return monthCal.numberOfMonthsInThisYear; },
                            getValue: function (index) {
                                monthCal.month = monthCal.firstMonthInThisYear;
                                monthCal.addMonths(index);
                                return formatMonth(datePatterns.month, calendar, DEFAULT_MONTH_PATTERN, monthCal);
                            }
                        };
                    };

                    var dateSource = function (yearIndex, monthIndex) {
                        dayCal.setDateTime(startDate);
                        dayCal.addYears(yearIndex);
                        dayCal.month = dayCal.firstMonthInThisYear;
                        dayCal.addMonths(monthIndex);
                        dayCal.day = dayCal.firstDayInThisMonth;

                        return {
                            getLength: function () { return dayCal.numberOfDaysInThisMonth; },
                            getValue: function (index) {
                                dayCal.day = dayCal.firstDayInThisMonth;
                                dayCal.addDays(index);
                                return formatDay(datePatterns.date, calendar, DEFAULT_DAY_PATTERN, dayCal);
                            }
                        };
                    };

                    return {
                        isRTL: isRTL,
                        forceLanguage: dateformat.resolvedLanguage,

                        order: order,

                        getDate: function (index, lastDate) {
                            var lastCal;

                            if (lastDate) {
                                tempCal.setDateTime(lastDate);
                                lastCal = { year: tempCal.year, month: tempCal.month, day: tempCal.day };
                            }

                            var c = tempCal;
                            c.setDateTime(startDate);
                            c.addYears(index.year);

                            var guessMonth;
                            if (c.firstMonthInThisYear > c.lastMonthInThisYear) {
                                if (index.month + c.firstMonthInThisYear > c.numberOfMonthsInThisYear) {
                                    guessMonth = index.month + c.firstMonthInThisYear - c.numberOfMonthsInThisYear;
                                } else {
                                    guessMonth = index.month + c.firstMonthInThisYear;
                                }
                                if (lastCal && lastCal.year !== c.year) {
                                    // Year has changed in some transitions in Thai Calendar, this will change the first month, and last month indices of the year.
                                    guessMonth = Math.max(Math.min(lastCal.month, c.numberOfMonthsInThisYear), 1);
                                }
                            } else {
                                if (lastCal && lastCal.year !== c.year) {
                                    // Year has changed in some transitions in Thai Calendar, this will change the first month, and last month indices of the year.
                                    guessMonth = Math.max(Math.min(lastCal.month, c.firstMonthInThisYear + c.numberOfMonthsInThisYear - 1), c.firstMonthInThisYear);
                                } else {
                                    guessMonth = Math.max(Math.min(index.month + c.firstMonthInThisYear, c.firstMonthInThisYear + c.numberOfMonthsInThisYear - 1), c.firstMonthInThisYear);
                                }
                            }
                            c.month = guessMonth;

                            var guessDay = Math.max(Math.min(index.date + c.firstDayInThisMonth, c.firstDayInThisMonth + c.numberOfDaysInThisMonth - 1), c.firstDayInThisMonth);
                            if (lastCal && (lastCal.year !== c.year || lastCal.month !== c.month)) {
                                guessDay = Math.max(Math.min(lastCal.day, c.firstDayInThisMonth + c.numberOfDaysInThisMonth - 1), c.firstDayInThisMonth);
                            }
                            c.day = c.firstDayInThisMonth;
                            c.addDays(guessDay - c.firstDayInThisMonth);
                            return c.getDateTime();
                        },
                        getIndex: function (date) {
                            var curDate = clamp(date);
                            tempCal.setDateTime(curDate);
                            var cur = { year: tempCal.year, era: tempCal.era };

                            var yearIndex = 0;

                            tempCal.setDateTime(startDate);
                            tempCal.month = 1;
                            yearIndex = yearDiff(tempCal, cur);

                            tempCal.setDateTime(curDate);
                            var monthIndex = tempCal.month - tempCal.firstMonthInThisYear;
                            if (monthIndex < 0) {
                                // A special case is in some ThaiCalendar years first month
                                // of the year is April, last month is March and month flow is wrap-around
                                // style; April, March .... November, December, January, February, March. So the first index
                                // will be 4 and last index will be 3. We are handling the case to convert this wraparound behavior
                                // into selected index.
                                monthIndex = tempCal.month - tempCal.firstMonthInThisYear + tempCal.numberOfMonthsInThisYear;
                            }
                            var dateIndex = tempCal.day - tempCal.firstDayInThisMonth;

                            var index = {
                                year: yearIndex,
                                month: monthIndex,
                                date: dateIndex
                            };

                            return index;
                        },
                        years: yearSource,
                        months: monthSource,
                        dates: dateSource
                    };

                },

                _getInformationJS: function (startDate, endDate) {
                    var minYear = startDate.getFullYear();
                    var maxYear = endDate.getFullYear();
                    var yearSource = {
                        getLength: function () { return Math.max(0, maxYear - minYear + 1); },
                        getValue: function (index) { return minYear + index; }
                    };

                    var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
                    var monthSource = function () {
                        return {
                            getLength: function () { return months.length; },
                            getValue: function (index) { return months[index]; },
                            getMonthNumber: function (index) { return Math.min(index, months.length - 1); }
                        };
                    };

                    var dateSource = function (yearIndex, monthIndex) {
                        var temp = new Date();
                        var year = yearSource.getValue(yearIndex);
                        // The +1 is needed to make using a day of 0 work correctly
                        var month = monthIndex + 1; // index is always correct, unlike getMonth which changes when the date is invalid
                        temp.setFullYear(year, month, 0);

                        var maxValue = temp.getDate();

                        return {
                            getLength: function () { return maxValue; },
                            getValue: function (index) { return "" + (index + 1); },
                            getDateNumber: function (index) { return Math.min(index + 1, maxValue); }
                        };
                    };

                    return {
                        order: ["month", "date", "year"],

                        getDate: function (index) {
                            return new Date(
                                yearSource.getValue(index.year),
                                monthSource(index.year).getMonthNumber(index.month),
                                dateSource(index.year, index.month).getDateNumber(index.date),
                                12, 0
                            );
                        },
                        getIndex: function (date) {
                            var yearIndex = 0;
                            var year = date.getFullYear();
                            if (year < minYear) {
                                yearIndex = 0;
                            } else if (year > this.maxYear) {
                                yearIndex = yearSource.getLength() - 1;
                            } else {
                                yearIndex = date.getFullYear() - minYear;
                            }

                            var monthIndex = Math.min(date.getMonth(), monthSource(yearIndex).getLength());

                            var dateIndex = Math.min(date.getDate() - 1, dateSource(yearIndex, monthIndex).getLength());

                            return {
                                year: yearIndex,
                                month: monthIndex,
                                date: dateIndex
                            };
                        },
                        years: yearSource,
                        months: monthSource,
                        dates: dateSource
                    };
                }
            });
            if (_WinRT.Windows.Globalization.Calendar && _WinRT.Windows.Globalization.DateTimeFormatting) {
                DatePicker.getInformation = DatePicker._getInformationWinRT;
            } else {
                DatePicker.getInformation = DatePicker._getInformationJS;
            }
            _Base.Class.mix(DatePicker, _Events.createEventProperties("change"));
            _Base.Class.mix(DatePicker, _Control.DOMEventMixin);
            return DatePicker;
        })
    });


});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

define('WinJS/Controls/TimePicker',[
    '../Core/_Global',
    '../Core/_WinRT',
    '../Core/_Base',
    '../Core/_BaseUtils',
    '../Core/_Events',
    '../Core/_Resources',
    '../Utilities/_Control',
    '../Utilities/_ElementUtilities',
    '../Utilities/_Hoverable',
    '../Utilities/_Select',
    'require-style!less/desktop/controls',
    'require-style!less/phone/controls'
    ], function timePickerInit(_Global, _WinRT, _Base, _BaseUtils, _Events, _Resources, _Control, _ElementUtilities, _Hoverable, _Select) {
    "use strict";

    _Base.Namespace.define("WinJS.UI", {
        /// <field>
        /// <summary locid="WinJS.UI.TimePicker">Allows users to select time values.</summary>
        /// <compatibleWith platform="Windows" minVersion="8.0"/>
        /// </field>
        /// <name locid="WinJS.UI.TimePicker_name">Time Picker</name>
        /// <icon src="ui_winjs.ui.timepicker.12x12.png" width="12" height="12" />
        /// <icon src="ui_winjs.ui.timepicker.16x16.png" width="16" height="16" />
        /// <htmlSnippet><![CDATA[<div data-win-control="WinJS.UI.TimePicker"></div>]]></htmlSnippet>
        /// <event name="change" locid="WinJS.UI.TimePicker_e:change">Occurs when the time changes.</event>
        /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
        /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
        /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
        TimePicker: _Base.Namespace._lazy(function () {
            // Constants definition
            var DEFAULT_MINUTE_PATTERN = "{minute.integer(2)}",
                DEFAULT_HOUR_PATTERN = "{hour.integer(1)}",
                DEFAULT_PERIOD_PATTERN = "{period.abbreviated(2)}";

            var strings = {
                get ariaLabel() { return _Resources._getWinJSString("ui/timePicker").value; },
                get selectHour() { return _Resources._getWinJSString("ui/selectHour").value; },
                get selectMinute() { return _Resources._getWinJSString("ui/selectMinute").value; },
                get selectAMPM() { return _Resources._getWinJSString("ui/selectAMPM").value; },
            };

            // date1 and date2 must be Date objects with their date portions set to the
            // sentinel date.
            var areTimesEqual = function (date1, date2) {
                return date1.getHours() === date2.getHours() &&
                    date1.getMinutes() === date2.getMinutes();
            };

            var TimePicker = _Base.Class.define(function TimePicker_ctor(element, options) {
                /// <signature helpKeyword="WinJS.UI.TimePicker.TimePicker">
                /// <summary locid="WinJS.UI.TimePicker.constructor">Initializes a new instance of the TimePicker control</summary>
                /// <param name="element" type="HTMLElement" domElement="true" locid="WinJS.UI.TimePicker.constructor_p:element">
                /// The DOM element associated with the TimePicker control.
                /// </param>
                /// <param name="options" type="Object" locid="WinJS.UI.TimePicker.constructor_p:options">
                /// The set of options to be applied initially to the TimePicker control.
                /// </param>
                /// <returns type="WinJS.UI.TimePicker" locid="WinJS.UI.TimePicker.constructor_returnValue">A constructed TimePicker control.</returns>
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </signature>

                // Default to current time
                this._currentTime = TimePicker._sentinelDate();

                element = element || _Global.document.createElement("div");
                _ElementUtilities.addClass(element, "win-disposable");
                element.winControl = this;

                var label = element.getAttribute("aria-label");
                if (!label) {
                    element.setAttribute("aria-label", strings.ariaLabel);
                }

                this._timePatterns = {
                    minute: null,
                    hour: null,
                    period: null
                };

                // Options should be set after the element is initialized which is
                // the same order of operation as imperatively setting options.
                this._init(element);
                _Control.setOptions(this, options);
            }, {
                _currentTime: null,
                _clock: null,
                _disabled: false,
                _hourElement: null,
                _hourControl: null,
                _minuteElement: null,
                _minuteControl: null,
                _ampmElement: null,
                _ampmControl: null,
                _minuteIncrement: 1,
                _timePatterns: {
                    minute: null,
                    hour: null,
                    period: null
                },
                _information: null,

                _addAccessibilityAttributes: function () {
                    //see http://www.w3.org/TR/wai-aria/rdf_model.png for details
                    this._domElement.setAttribute("role", "group");

                    this._hourElement.setAttribute("aria-label", strings.selectHour);
                    this._minuteElement.setAttribute("aria-label", strings.selectMinute);
                    if (this._ampmElement) {
                        this._ampmElement.setAttribute("aria-label", strings.selectAMPM);
                    }
                },

                _addControlsInOrder: function (info) {
                    var that = this;
                    info.order.forEach(function (s, index) {
                        switch (s) {
                            case "hour":
                                that._domElement.appendChild(that._hourElement);
                                _ElementUtilities.addClass(that._hourElement, "win-order" + index);
                                break;
                            case "minute":
                                that._domElement.appendChild(that._minuteElement);
                                _ElementUtilities.addClass(that._minuteElement, "win-order" + index);
                                break;
                            case "period":
                                if (that._ampmElement) {
                                    that._domElement.appendChild(that._ampmElement);
                                    _ElementUtilities.addClass(that._ampmElement, "win-order" + index);
                                }
                                break;
                        }
                    });
                },

                dispose: function () {
                    /// <signature helpKeyword="WinJS.UI.TimePicker.dispose">
                    /// <summary locid="WinJS.UI.TimePicker.dispose">
                    /// Disposes this TimePicker.
                    /// </summary>
                    /// <compatibleWith platform="Windows" minVersion="8.0"/>
                    /// </signature>
                },

                /// <field type="String" locid="WinJS.UI.TimePicker.clock" helpKeyword="WinJS.UI.TimePicker.clock">
                /// Gets or sets the type of clock to display (12HourClock or 24HourClock). It defaults to the user setting.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                clock: {
                    get: function () {
                        return this._clock;
                    },
                    set: function (value) {
                        if (this._clock !== value) {
                            this._clock = value;
                            this._init();
                        }
                    }
                },

                /// <field type="Date" locid="WinJS.UI.TimePicker.current" helpKeyword="WinJS.UI.TimePicker.current">
                /// Gets or sets the current date (and time) of the TimePicker.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                current: {
                    get: function () {
                        var cur = this._currentTime;
                        if (cur) {
                            var time = TimePicker._sentinelDate();
                            time.setHours(cur.getHours()); // accounts for AM/PM
                            time.setMinutes(this._getMinutesIndex(cur) * this.minuteIncrement);
                            time.setSeconds(0);
                            time.setMilliseconds(0);
                            return time;
                        } else {
                            return cur;
                        }
                    },
                    set: function (value) {
                        var newTime;
                        if (typeof (value) === "string") {
                            newTime = TimePicker._sentinelDate();
                            newTime.setTime(Date.parse(newTime.toDateString() + " " + value));
                        } else {
                            newTime = TimePicker._sentinelDate();
                            newTime.setHours(value.getHours());
                            newTime.setMinutes(value.getMinutes());
                        }

                        var oldTime = this._currentTime;
                        if (!areTimesEqual(oldTime, newTime)) {
                            this._currentTime = newTime;

                            this._updateDisplay();
                        }
                    }
                },

                /// <field type="Boolean" locid="WinJS.UI.TimePicker.disabled" helpKeyword="WinJS.UI.TimePicker.disabled">
                /// Specifies whether the TimePicker is disabled.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                disabled: {
                    get: function () { return this._disabled; },
                    set: function (value) {
                        if (this._disabled !== value) {
                            this._disabled = value;
                            if (this._hourControl) {
                                this._hourControl.setDisabled(value);
                                this._minuteControl.setDisabled(value);
                            }
                            if (this._ampmControl) {
                                this._ampmControl.setDisabled(value);
                            }
                        }
                    }
                },

                /// <field type="HTMLElement" domElement="true" hidden="true" locid="WinJS.UI.TimePicker.element" helpKeyword="WinJS.UI.TimePicker.element">
                /// Gets the DOM element for the TimePicker.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                element: {
                    get: function () { return this._domElement; }
                },


                _init: function (element) {
                    this._setElement(element);
                    this._updateDisplay();
                },

                /// <field type="String" locid="WinJS.UI.TimePicker.hourPattern" helpKeyword="WinJS.UI.TimePicker.hourPattern">
                /// Gets or sets the display pattern for the hour.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                hourPattern: {
                    get: function () { return this._timePatterns.hour.pattern; },
                    set: function (value) {
                        if (this._timePatterns.hour !== value) {
                            this._timePatterns.hour = value;
                            this._init();
                        }
                    }

                },

                _getHoursAmpm: function (time) {
                    var hours24 = time.getHours();
                    if (this._ampmElement) {
                        if (hours24 === 0) {
                            return { hours: 12, ampm: 0 };
                        } else if (hours24 < 12) {
                            return { hours: hours24, ampm: 0 };
                        }
                        return { hours: hours24 - 12, ampm: 1 };
                    }

                    return { hours: hours24 };
                },

                _getHoursIndex: function (hours) {
                    if (this._ampmElement && hours === 12) {
                        return 0;
                    }
                    return hours;
                },

                _getMinutesIndex: function (time) {
                    return parseInt(time.getMinutes() / this.minuteIncrement);
                },

                /// <field type="Number" integer="true" locid="WinJS.UI.TimePicker.minuteIncrement" helpKeyword="WinJS.UI.TimePicker.minuteIncrement">
                /// Gets or sets the minute increment. For example, "15" specifies that the TimePicker minute control should display only the choices 00, 15, 30, 45.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                minuteIncrement: {
                    //prevent divide by 0, and leave user's input intact
                    get: function () { return Math.max(1, Math.abs(this._minuteIncrement | 0) % 60); },
                    set: function (value) {
                        if (this._minuteIncrement !== value) {
                            this._minuteIncrement = value;
                            this._init();
                        }
                    }

                },

                /// <field type="String" locid="WinJS.UI.TimePicker.minutePattern" helpKeyword="WinJS.UI.TimePicker.minutePattern">
                /// Gets or sets the display pattern for the minute.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                minutePattern: {
                    get: function () { return this._timePatterns.minute.pattern; },
                    set: function (value) {
                        if (this._timePatterns.minute !== value) {
                            this._timePatterns.minute = value;
                            this._init();
                        }
                    }
                },

                /// <field type="String" locid="WinJS.UI.TimePicker.periodPattern" helpKeyword="WinJS.UI.TimePicker.periodPattern">
                /// Gets or sets the display pattern for the period.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                periodPattern: {
                    get: function () { return this._timePatterns.period.pattern; },
                    set: function (value) {

                        if (this._timePatterns.period !== value) {
                            this._timePatterns.period = value;
                            this._init();
                        }
                    }
                },

                _setElement: function (element) {
                    this._domElement = this._domElement || element;
                    if (!this._domElement) { return; }

                    var info = TimePicker.getInformation(this.clock, this.minuteIncrement, this._timePatterns);
                    this._information = info;

                    if (info.forceLanguage) {
                        this._domElement.setAttribute("lang", info.forceLanguage);
                        this._domElement.setAttribute("dir", info.isRTL ? "rtl" : "ltr");
                    }

                    _ElementUtilities.empty(this._domElement);
                    _ElementUtilities.addClass(this._domElement, "win-timepicker");

                    this._hourElement = _Global.document.createElement("select");
                    _ElementUtilities.addClass(this._hourElement, "win-timepicker-hour");

                    this._minuteElement = _Global.document.createElement("select");
                    _ElementUtilities.addClass(this._minuteElement, "win-timepicker-minute");

                    this._ampmElement = null;
                    if (info.clock === "12HourClock") {
                        this._ampmElement = _Global.document.createElement("select");
                        _ElementUtilities.addClass(this._ampmElement, "win-timepicker-period");
                    }

                    this._addControlsInOrder(info);

                    var hoursAmpm = this._getHoursAmpm(this.current);
                    this._hourControl = new _Select._Select(this._hourElement, {
                        dataSource: this._getInfoHours(),
                        disabled: this.disabled,
                        index: this._getHoursIndex(hoursAmpm.hours)
                    });
                    this._minuteControl = new _Select._Select(this._minuteElement, {
                        dataSource: info.minutes,
                        disabled: this.disabled,
                        index: this._getMinutesIndex(this.current)
                    });
                    this._ampmControl = null;
                    if (this._ampmElement) {
                        this._ampmControl = new _Select._Select(this._ampmElement, {
                            dataSource: info.periods,
                            disabled: this.disabled,
                            index: hoursAmpm.ampm
                        });
                    }

                    this._wireupEvents();
                    this._updateValues();
                    this._addAccessibilityAttributes();
                },

                _getInfoHours: function () {
                    return this._information.hours;
                },

                _updateLayout: function () {
                    if (!this._domElement) {
                        return;
                    }
                    this._updateValues();
                },

                _updateValues: function () {
                    if (this._hourControl) {
                        var hoursAmpm = this._getHoursAmpm(this.current);
                        if (this._ampmControl) {
                            this._ampmControl.index = hoursAmpm.ampm;
                        }
                        this._hourControl.index = this._getHoursIndex(hoursAmpm.hours);
                        this._minuteControl.index = this._getMinutesIndex(this.current);
                    }
                },

                _updateDisplay: function () {
                    //Render display index based on constraints (minuteIncrement)
                    //Will not modify current time

                    var hoursAmpm = this._getHoursAmpm(this.current);

                    if (this._ampmControl) {
                        this._ampmControl.index = hoursAmpm.ampm;
                    }

                    if (this._hourControl) {
                        this._hourControl.index = this._getHoursIndex(hoursAmpm.hours);
                        this._minuteControl.index = this._getMinutesIndex(this.current);
                    }
                },

                _wireupEvents: function () {
                    var that = this;

                    var fixupHour = function () {
                        var hour = that._hourControl.index;
                        if (that._ampmElement) {
                            if (that._ampmControl.index === 1) {
                                if (hour !== 12) {
                                    hour += 12;
                                }
                            }
                        }
                        return hour;
                    };

                    var changed = function () {
                        var hour = fixupHour();
                        that._currentTime.setHours(hour);

                        that._currentTime.setMinutes(that._minuteControl.index * that.minuteIncrement);
                    };

                    this._hourElement.addEventListener("change", changed, false);
                    this._minuteElement.addEventListener("change", changed, false);
                    if (this._ampmElement) {
                        this._ampmElement.addEventListener("change", changed, false);
                    }
                }
            }, {
                _sentinelDate: function () {
                    // This is July 15th, 2011 as our sentinel date. There are no known
                    //  daylight savings transitions that happened on that date.
                    var current = new Date();
                    return new Date(2011, 6, 15, current.getHours(), current.getMinutes());
                },
                _getInformationWinRT: function (clock, minuteIncrement, timePatterns) {
                    var newFormatter = function (pattern, defaultPattern) {
                        var dtf = _WinRT.Windows.Globalization.DateTimeFormatting;
                        pattern = !pattern ? defaultPattern : pattern;
                        var formatter = new dtf.DateTimeFormatter(pattern);
                        if (clock) {
                            formatter = dtf.DateTimeFormatter(pattern, formatter.languages, formatter.geographicRegion, formatter.calendar, clock);
                        }
                        return formatter;
                    };

                    var glob = _WinRT.Windows.Globalization;
                    var calendar = new glob.Calendar();
                    if (clock) {
                        calendar = new glob.Calendar(calendar.languages, calendar.getCalendarSystem(), clock);
                    }
                    calendar.setDateTime(TimePicker._sentinelDate());

                    var computedClock = calendar.getClock();
                    var numberOfHours = 24;
                    numberOfHours = calendar.numberOfHoursInThisPeriod;

                    var periods = (function () {
                        var periodFormatter = newFormatter(timePatterns.period, DEFAULT_PERIOD_PATTERN);
                        return {
                            getLength: function () { return 2; },
                            getValue: function (index) {
                                var date = TimePicker._sentinelDate();
                                if (index === 0) {
                                    date.setHours(1);
                                    var am = periodFormatter.format(date);
                                    return am;
                                }
                                if (index === 1) {
                                    date.setHours(13);
                                    var pm = periodFormatter.format(date);
                                    return pm;
                                }
                                return null;
                            }
                        };
                    })();

                    // Determine minute format from the DateTimeFormatter
                    var minutes = (function () {
                        var minuteFormatter = newFormatter(timePatterns.minute, DEFAULT_MINUTE_PATTERN);
                        var now = TimePicker._sentinelDate();
                        return {
                            getLength: function () { return 60 / minuteIncrement; },
                            getValue: function (index) {
                                var display = index * minuteIncrement;
                                now.setMinutes(display);
                                return minuteFormatter.format(now);
                            }
                        };
                    })();


                    // Determine hour format from the DateTimeFormatter
                    var hours = (function () {
                        var hourFormatter = newFormatter(timePatterns.hour, DEFAULT_HOUR_PATTERN);
                        var now = TimePicker._sentinelDate();
                        return {
                            getLength: function () { return numberOfHours; },
                            getValue: function (index) {
                                now.setHours(index);
                                return hourFormatter.format(now);
                            }
                        };
                    })();

                    // Determine the order of the items from the DateTimeFormatter.
                    // "hour minute" also returns the period (if needed).
                    //
                    var hourMinuteFormatter = newFormatter("hour minute");
                    var pattern = hourMinuteFormatter.patterns[0];
                    var order = ["hour", "minute"];

                    var indexes = {
                        period: pattern.indexOf("{period"),
                        hour: pattern.indexOf("{hour"),
                        minute: pattern.indexOf("{minute")
                    };
                    if (indexes.period > -1) {
                        order.push("period");
                    }


                    var DateTimeFormatter = _WinRT.Windows.Globalization.DateTimeFormatting.DateTimeFormatter;
                    var dtf = new DateTimeFormatter("month.full", _WinRT.Windows.Globalization.ApplicationLanguages.languages, "ZZ", "GregorianCalendar", "24HourClock");
                    var pat = dtf.patterns[0];
                    var isRTL = pat.charCodeAt(0) === 8207;

                    if (isRTL) {
                        var temp = indexes.hour;
                        indexes.hour = indexes.minute;
                        indexes.minute = temp;
                    }

                    order.sort(function (a, b) {
                        if (indexes[a] < indexes[b]) {
                            return -1;
                        } else if (indexes[a] > indexes[b]) {
                            return 1;
                        } else {
                            return 0;
                        }
                    });

                    return { minutes: minutes, hours: hours, clock: computedClock, periods: periods, order: order, forceLanguage: hourMinuteFormatter.resolvedLanguage, isRTL: isRTL };
                },
                _getInformationJS: function (clock, minuteIncrement) {
                    var hours = [12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];

                    var minutes = {};
                    minutes.getLength = function () { return 60 / minuteIncrement; };
                    minutes.getValue = function (index) {
                        var display = index * minuteIncrement;
                        if (display < 10) {
                            return "0" + display.toString();
                        } else {
                            return display.toString();
                        }
                    };

                    var order = ["hour", "minute", "period"];
                    if (clock === "24HourClock") {
                        hours = ["00", "01", "02", "03", "04", "05", "06", "07", "08", "09", 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23];
                        order = ["hour", "minute"];
                    }
                    return { minutes: minutes, hours: hours, clock: clock || "12HourClock", periods: ["AM", "PM"], order: order };
                }
            });
            if (_WinRT.Windows.Globalization.DateTimeFormatting && _WinRT.Windows.Globalization.Calendar && _WinRT.Windows.Globalization.ApplicationLanguages) {
                TimePicker.getInformation = TimePicker._getInformationWinRT;
            } else {
                TimePicker.getInformation = TimePicker._getInformationJS;
            }
            _Base.Class.mix(TimePicker, _Events.createEventProperties("change"));
            _Base.Class.mix(TimePicker, _Control.DOMEventMixin);
            return TimePicker;
        })
    });


});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Back Button
define('WinJS/Controls/BackButton',[
    '../Core/_Global',
    '../Core/_Base',
    '../Core/_ErrorFromName',
    '../Core/_Resources',
    '../Navigation',
    '../Utilities/_Control',
    '../Utilities/_ElementUtilities',
    '../Utilities/_Hoverable',
    'require-style!less/desktop/controls',
    'require-style!less/phone/controls'
    ], function backButtonInit(_Global, _Base, _ErrorFromName, _Resources, Navigation, _Control, _ElementUtilities, _Hoverable) {
    "use strict";

    var Key = _ElementUtilities.Key;

    // Class Names
    var navigationBackButtonClass = 'win-navigation-backbutton';
    var glyphClass = "win-back";

    // CONSTANTS
    var MOUSE_BACK_BUTTON = 3;

    // Create Singleton for global event registering/unregistering. This Singleton should only be created once.
    // Here the function 'returnBackButtonSingelton' is called immediateley and its result is the singleton object.
    var singleton = (function createBackButtonSingleton() {

        /* Step 1: Build JavaScript closure */

        function hookUpBackButtonGlobalEventHandlers() {
            // Subscribes to global events on the window object
            _Global.addEventListener('keyup', backButtonGlobalKeyUpHandler, false);
            _ElementUtilities._addEventListener(_Global, 'pointerup', backButtonGlobalMSPointerUpHandler, false);
        }

        function unHookBackButtonGlobalEventHandlers() {
            // Unsubscribes from global events on the window object
            _Global.removeEventListener('keyup', backButtonGlobalKeyUpHandler, false);
            _ElementUtilities._removeEventListener(_Global, 'pointerup', backButtonGlobalMSPointerUpHandler, false);
        }

        function backButtonGlobalKeyUpHandler(event) {
            // Navigates back when (alt + left) or BrowserBack keys are released.
            if ((event.keyCode === Key.leftArrow && event.altKey && !event.shiftKey && !event.ctrlKey) || (event.keyCode === Key.browserBack)) {
                Navigation.back();
            }
        }

        function backButtonGlobalMSPointerUpHandler(event) {
            // Responds to clicks to enable navigation using 'back' mouse buttons.
            if (event.button === MOUSE_BACK_BUTTON) {
                Navigation.back();
            }
        }

        // Singleton reference count for registering and unregistering global event handlers.
        var backButtonReferenceCount = 0; //

        /* Step 2: Return Singleton object literal */
        return {
            addRef: function () {
                if (backButtonReferenceCount === 0) {
                    hookUpBackButtonGlobalEventHandlers();
                }
                backButtonReferenceCount++;
            },
            release: function () {
                if (backButtonReferenceCount > 0) { // Ensure count won't become negative.
                    backButtonReferenceCount--;
                    if (backButtonReferenceCount === 0) {
                        unHookBackButtonGlobalEventHandlers();
                    }
                }
            },
            getCount: function () { // Return the value of the reference count. Useful for unit testing.
                return backButtonReferenceCount;
            }
        };
    }()); // Immediate invoke creates and returns the Singleton

    _Base.Namespace.define("WinJS.UI", {
        /// <field>
        /// <summary locid="WinJS.UI.BackButton">
        /// Provides backwards navigation functionality.
        /// </summary>
        /// <compatibleWith platform="Windows" minVersion="8.1"/>
        /// </field>
        /// <icon src="ui_winjs.ui.backbutton.12x12.png" width="12" height="12" />
        /// <icon src="ui_winjs.ui.backbutton.16x16.png" width="16" height="16" />
        /// <htmlSnippet><![CDATA[<button data-win-control="WinJS.UI.BackButton"></button>]]></htmlSnippet>
        /// <part name="BackButton" class="win-navigation-backbutton" locid="WinJS.UI.BackButton_part:BackButton">The BackButton control itself</part>
        /// <part name="BackArrowGlyph" class="win-back" locid="WinJS.UI.BackButton_part:BackArrowGlyph">The Back Arrow glyph</part>
        /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
        /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
        /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
        BackButton: _Base.Namespace._lazy(function () {
            // Statics
            var strings = {
                get ariaLabel() { return _Resources._getWinJSString("ui/backbuttonarialabel").value; },
                get duplicateConstruction() { return "Invalid argument: Controls may only be instantiated one time for each DOM element"; },
                get badButtonElement() { return "Invalid argument: For a button, toggle, or flyout command, the element must be null or a button element"; }
            };

            var BackButton = _Base.Class.define(function BackButton_ctor(element, options) {
                /// <signature helpKeyword="WinJS.UI.BackButton.BackButton">
                /// <summary locid="WinJS.UI.BackButton.constructor">
                /// Creates a new BackButton control
                /// </summary>
                /// <param name="element" domElement="true" locid="WinJS.UI.BackButton.constructor_p:element">
                /// The DOM element that will host the control. If this parameter is null, this constructor creates one for you.
                /// </param>
                /// <param name="options" type="Object" isOptional="true" locid="WinJS.UI.MenuBackButtonCommand.constructor_p:options">
                /// An object that contains one or more property/value pairs to apply to the new control. Each property of the options object corresponds to
                /// one of the control's properties or events.
                /// </param>
                /// <returns type="WinJS.UI.BackButton" locid="WinJS.UI.BackButton.constructor_returnValue">
                /// A BackButton control.
                /// </returns>
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </signature>

                // Check to make sure we weren't duplicated
                if (element && element.winControl) {
                    throw new _ErrorFromName("WinJS.UI.BackButton.DuplicateConstruction", strings.duplicateConstruction);
                }

                this._element = element || _Global.document.createElement("button");
                options = options || {};

                this._initializeButton(); // This will also set the aria-label and tooltip

                this._disposed = false;

                // Remember ourselves
                this._element.winControl = this;

                _Control.setOptions(this, options);

                // Add event handlers for this back button instance
                this._buttonClickHandler = this._handleBackButtonClick.bind(this);
                this._element.addEventListener('click', this._buttonClickHandler, false);
                this._navigatedHandler = this._handleNavigatedEvent.bind(this);
                Navigation.addEventListener('navigated', this._navigatedHandler, false);

                // Increment reference count / manage add global event handlers
                singleton.addRef();
            }, {

                /// <field type="HTMLElement" domElement="true" readonly="true" hidden="true" locid="WinJS.UI.BackButton.element" helpKeyword="WinJS.UI.BackButton.element">
                /// Gets the DOM element that hosts the BackButton control.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                element: {
                    get: function () {
                        return this._element;
                    }
                },

                dispose: function () {
                    /// <signature helpKeyword="WinJS.UI.BackButton.dispose">
                    /// <summary locid="WinJS.UI.BackButton.dispose">
                    /// Disposes this control.
                    /// </summary>
                    /// <compatibleWith platform="Windows" minVersion="8.1"/>
                    /// </signature>
                    if (this._disposed) {
                        return;
                    }
                    this._disposed = true; // Mark this control as disposed.

                    // Remove 'navigated' eventhandler for this BackButton
                    Navigation.removeEventListener('navigated', this._navigatedHandler, false);

                    singleton.release(); // Decrement reference count.

                },

                refresh: function () {
                    /// <signature helpKeyword="WinJS.UI.BackButton.refresh">
                    /// <summary locid="WinJS.UI.BackButton.refresh">
                    /// Sets the 'disabled' attribute to correct the value based on the current navigation history stack.
                    /// </summary>
                    /// <compatibleWith platform="Windows" minVersion="8.1"/>
                    /// </signature>
                    if (Navigation.canGoBack) {
                        this._element.disabled = false;
                    } else {
                        this._element.disabled = true;
                    }
                },

                _initializeButton: function () {
                    //Final EN-US HTML should be:
                    //<button class="win-navigation-backbutton" aria-label="Back" title="Back" type="button"><span class="win-back"></span></button>
                    //Button will automatically be disabled if WinJS.Navigation.history.canGoBack is false.

                    // Verify the HTML is a button
                    if (this._element.tagName !== "BUTTON") {
                        throw new _ErrorFromName("WinJS.UI.BackButton.BadButtonElement", strings.badButtonElement);
                    }

                    // Attach our css classes
                    _ElementUtilities.addClass(this._element, navigationBackButtonClass);

                    // Attach disposable class.
                    _ElementUtilities.addClass(this._element, "win-disposable");

                    // Create inner glyph element
                    this._element.innerHTML = '<span class="' + glyphClass + '"></span>';

                    // Set the 'disabled' property to the correct value based on the current navigation history stack.
                    this.refresh();

                    // Set Aria-label and native tooltip to the same localized string equivalent of "Back"
                    this._element.setAttribute("aria-label", strings.ariaLabel);
                    this._element.setAttribute("title", strings.ariaLabel);

                    // Explicitly set type attribute to avoid the default <button> "submit" type.
                    this._element.setAttribute("type", "button");
                },

                _handleNavigatedEvent: function () {
                    // Handles WinJS.Navigation 'navigated' behavior
                    this.refresh();
                },

                _handleBackButtonClick: function () {
                    // Handles BackButton 'click' behavior
                    Navigation.back();
                }

            });
            // Private Static Method.
            BackButton._getReferenceCount = function () {
                return singleton.getCount(); // Expose this for Unit testing.
            };
            _Base.Class.mix(BackButton, _Control.DOMEventMixin);
            return BackButton;
        })
    });

});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/Tooltip',[
    'exports',
    '../Core/_Global',
    '../Core/_WinRT',
    '../Core/_Base',
    '../Core/_BaseUtils',
    '../Core/_Events',
    '../Animations',
    '../Animations/_TransitionAnimation',
    '../Utilities/_Control',
    '../Utilities/_Dispose',
    '../Utilities/_ElementUtilities',
    '../Utilities/_Hoverable',
    'require-style!less/desktop/controls',
    'require-style!less/phone/controls'
    ], function tooltipInit(exports, _Global, _WinRT, _Base, _BaseUtils, _Events, Animations, _TransitionAnimation, _Control, _Dispose, _ElementUtilities, _Hoverable) {
    "use strict";

    // Tooltip control implementation
    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {
        /// <field>
        /// <summary locid="WinJS.UI.Tooltip">
        /// Displays a tooltip that can contain images and formatting.
        /// </summary>
        /// <compatibleWith platform="Windows" minVersion="8.0"/>
        /// </field>
        /// <icon src="ui_winjs.ui.tooltip.12x12.png" width="12" height="12" />
        /// <icon src="ui_winjs.ui.tooltip.16x16.png" width="16" height="16" />
        /// <htmlSnippet supportsContent="true"><![CDATA[<div style="display:inline-block;" data-win-control="WinJS.UI.Tooltip" data-win-options="{innerHTML:'Tooltip content goes here'}"></div>]]></htmlSnippet>
        /// <event name="beforeopen" bubbles="false" locid="WinJS.UI.Tooltip_e:beforeopen">Raised when the tooltip is about to appear.</event>
        /// <event name="opened" bubbles="false" locid="WinJS.UI.Tooltip_e:opened">Raised when the tooltip is showing.</event>
        /// <event name="beforeclose" bubbles="false" locid="WinJS.UI.Tooltip_e:beforeclose">Raised when the tooltip is about to become hidden.</event>
        /// <event name="closed" bubbles="false" locid="WinJS.UI.Tooltip_e:close">Raised when the tooltip is hidden.</event>
        /// <part name="tooltip" class="win-tooltip" locid="WinJS.UI.Tooltip_e:tooltip">The entire Tooltip control.</part>
        /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
        /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
        /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
        Tooltip: _Base.Namespace._lazy(function () {
            var lastCloseTime = 0;
            var Key = _ElementUtilities.Key;

            // Constants definition
            var DEFAULT_PLACEMENT = "top";
            var DELAY_INITIAL_TOUCH_SHORT = _TransitionAnimation._animationTimeAdjustment(400);
            var DELAY_INITIAL_TOUCH_LONG = _TransitionAnimation._animationTimeAdjustment(1200);
            var DEFAULT_MOUSE_HOVER_TIME = _TransitionAnimation._animationTimeAdjustment(400); // 0.4 second
            var DEFAULT_MESSAGE_DURATION = _TransitionAnimation._animationTimeAdjustment(5000); // 5 secs
            var DELAY_RESHOW_NONINFOTIP_TOUCH = _TransitionAnimation._animationTimeAdjustment(0);
            var DELAY_RESHOW_NONINFOTIP_NONTOUCH = _TransitionAnimation._animationTimeAdjustment(600);
            var DELAY_RESHOW_INFOTIP_TOUCH = _TransitionAnimation._animationTimeAdjustment(400);
            var DELAY_RESHOW_INFOTIP_NONTOUCH = _TransitionAnimation._animationTimeAdjustment(600);
            var RESHOW_THRESHOLD = _TransitionAnimation._animationTimeAdjustment(200);
            var HIDE_DELAY_MAX = _TransitionAnimation._animationTimeAdjustment(300000); // 5 mins
            var OFFSET_KEYBOARD = 12;
            var OFFSET_MOUSE = 20;
            var OFFSET_TOUCH = 45;
            var OFFSET_PROGRAMMATIC_TOUCH = 20;
            var OFFSET_PROGRAMMATIC_NONTOUCH = 12;
            var SAFETY_NET_GAP = 1; // We set a 1-pixel gap between the right or bottom edge of the tooltip and the viewport to avoid possible re-layout
            var PT_MOUSE = _ElementUtilities._MSPointerEvent.MSPOINTER_TYPE_MOUSE || "mouse"; // pointer type to indicate a mouse event
            var PT_TOUCH = _ElementUtilities._MSPointerEvent.MSPOINTER_TYPE_TOUCH || "touch"; // pointer type to indicate a touch event

            var EVENTS_INVOKE = { "keyup": "", "pointerover": "", "pointerdown": "" },
                EVENTS_UPDATE = { "pointermove": "" },
                EVENTS_DISMISS = { "pointerdown": "", "keydown": "", "focusout": "", "pointerout": "", "pointercancel": "", "pointerup": "" },
                EVENTS_BY_CHILD = { "pointerover": "", "pointerout": "" };

            function isInvokeEvent(eventType, pointerType) {
                if (eventType === "pointerdown") {
                    return pointerType === PT_TOUCH;
                } else {
                    return eventType in EVENTS_INVOKE;
                }
            }

            function isDismissEvent(eventType, pointerType) {
                if (eventType === "pointerdown") {
                    return pointerType !== PT_TOUCH;
                } else {
                    return eventType in EVENTS_DISMISS;
                }
            }

            // CSS class names
            var msTooltip = "win-tooltip",
            msTooltipPhantom = "win-tooltip-phantom";

            // Global attributes
            var mouseHoverTime = DEFAULT_MOUSE_HOVER_TIME,
                nonInfoTooltipNonTouchShowDelay = 2 * mouseHoverTime,
                infoTooltipNonTouchShowDelay = 2.5 * mouseHoverTime,
                messageDuration = DEFAULT_MESSAGE_DURATION,
                isLeftHanded = false;

            var hasInitWinRTSettings = false;

            var createEvent = _Events._createEventProperty;

            return _Base.Class.define(function Tooltip_ctor(anchorElement, options) {
                /// <signature helpKeyword="WinJS.UI.Tooltip.Tooltip">
                /// <summary locid="WinJS.UI.Tooltip.constructor">
                /// Creates a new Tooltip.
                /// </summary>
                /// <param name="element" domElement="true" locid="WinJS.UI.Tooltip.constructor_p:element">
                /// The DOM element that hosts the Tooltip.
                /// </param>
                /// <param name="options" type="Object" locid="WinJS.UI.Tooltip.constructor_p:options">
                /// An object that contains one or more property/value pairs to apply to the new control.
                /// Each property of the options object corresponds to one of the control's properties or events.
                /// Event names must begin with "on". For example, to provide a handler for the opened event,
                /// add a property named "onopened" to the options object and set its value to the event handler.
                /// This parameter is optional.
                /// </param>
                /// <returns type="WinJS.UI.Tooltip" locid="WinJS.UI.Tooltip.constructor_returnValue">
                /// The new Tooltip.
                /// </returns>
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </signature>
                anchorElement = anchorElement || _Global.document.createElement("div");

                var tooltip = _ElementUtilities.data(anchorElement).tooltip;
                if (tooltip) {
                    return tooltip;
                }

                // Set system attributes if it is in WWA, otherwise, use the default values
                if (!hasInitWinRTSettings && _WinRT.Windows.UI.ViewManagement.UISettings) { // in WWA
                    var uiSettings = new _WinRT.Windows.UI.ViewManagement.UISettings();
                    mouseHoverTime = _TransitionAnimation._animationTimeAdjustment(uiSettings.mouseHoverTime);
                    nonInfoTooltipNonTouchShowDelay = 2 * mouseHoverTime;
                    infoTooltipNonTouchShowDelay = 2.5 * mouseHoverTime;
                    messageDuration = _TransitionAnimation._animationTimeAdjustment(uiSettings.messageDuration * 1000);  // uiSettings.messageDuration is in seconds.
                    var handedness = uiSettings.handPreference;
                    isLeftHanded = (handedness === _WinRT.Windows.UI.ViewManagement.HandPreference.leftHanded);
                }
                hasInitWinRTSettings = true;

                // Need to initialize properties
                this._disposed = false;
                this._placement = DEFAULT_PLACEMENT;
                this._infotip = false;
                this._innerHTML = null;
                this._contentElement = null;
                this._extraClass = null;
                this._lastContentType = "html";
                this._anchorElement = anchorElement;
                this._domElement = null;
                this._phantomDiv = null;
                this._triggerByOpen = false;
                this._eventListenerRemoveStack = [];

                // To handle keyboard navigation
                this._lastKeyOrBlurEvent = null;
                this._currentKeyOrBlurEvent = null;

                // Remember ourselves
                anchorElement.winControl = this;
                _ElementUtilities.addClass(anchorElement, "win-disposable");

                // If anchor element's title is defined, set as the default tooltip content
                if (anchorElement.title) {
                    this._innerHTML = this._anchorElement.title;
                    this._anchorElement.removeAttribute("title");
                }

                _Control.setOptions(this, options);
                this._events();
                _ElementUtilities.data(anchorElement).tooltip = this;
            }, {
                /// <field type="String" locid="WinJS.UI.Tooltip.innerHTML" helpKeyword="WinJS.UI.Tooltip.innerHTML">
                /// Gets or sets the HTML content of the Tooltip.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                innerHTML: {
                    get: function () {
                        return this._innerHTML;
                    },
                    set: function (value) {
                        this._innerHTML = value;
                        if (this._domElement) {
                            // If we set the innerHTML to null or "" while tooltip is up, we should close it
                            if (!this._innerHTML || this._innerHTML === "") {
                                this._onDismiss();
                                return;
                            }
                            this._domElement.innerHTML = value;
                            this._position();
                        }
                        this._lastContentType = "html";
                    }
                },

                /// <field type="HTMLElement" hidden="true" locid="WinJS.UI.Tooltip.element" helpKeyword="WinJS.UI.Tooltip.element">
                /// Gets or sets the DOM element that hosts the Tooltip.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                element: {
                    get: function () {
                        return this._anchorElement;
                    }
                },

                /// <field type="HTMLElement" locid="WinJS.UI.Tooltip.contentElement" helpKeyword="WinJS.UI.Tooltip.contentElement" potentialValueSelector="div[style='display: none;']>div[id], div[style='display: none;']>div[class]">
                /// Gets or sets the DOM element that is the content for the ToolTip.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                contentElement: {
                    get: function () {
                        return this._contentElement;
                    },
                    set: function (value) {
                        this._contentElement = value;
                        if (this._domElement) {
                            // If we set the contentElement to null while tooltip is up, we should close it
                            if (!this._contentElement) {
                                this._onDismiss();
                                return;
                            }
                            this._domElement.innerHTML = "";
                            this._domElement.appendChild(this._contentElement);
                            this._position();
                        }
                        this._lastContentType = "element";
                    }
                },

                /// <field type="String" oamOptionsDatatype="WinJS.UI.Tooltip.placement" locid="WinJS.UI.Tooltip.placement" helpKeyword="WinJS.UI.Tooltip.placement">
                /// Gets or sets the position for the Tooltip relative to its target element: top, bottom, left or right.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                placement: {
                    get: function () {
                        return this._placement;
                    },
                    set: function (value) {
                        if (value !== "top" && value !== "bottom" && value !== "left" && value !== "right") {
                            value = DEFAULT_PLACEMENT;
                        }
                        this._placement = value;
                        if (this._domElement) {
                            this._position();
                        }
                    }
                },

                /// <field type="Boolean" locid="WinJS.UI.Tooltip.infotip" helpKeyword="WinJS.UI.Tooltip.infotip">
                /// Gets or sets a value that specifies whether the Tooltip is an infotip, a tooltip that contains
                /// a lot of info and should be displayed for longer than a typical Tooltip.
                /// The default value is false.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                infotip: {
                    get: function () {
                        return this._infotip;
                    },
                    set: function (value) {
                        this._infotip = !!value; //convert the value to boolean
                    }
                },

                /// <field type="String" locid="WinJS.UI.Tooltip.extraClass" helpKeyword="WinJS.UI.Tooltip.extraClass" isAdvanced="true">
                /// Gets or sets additional CSS classes to apply to the Tooltip control's host element.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                extraClass: {
                    get: function () {
                        return this._extraClass;
                    },
                    set: function (value) {
                        this._extraClass = value;
                    }
                },

                /// <field type="Function" locid="WinJS.UI.Tooltip.onbeforeopen" helpKeyword="WinJS.UI.Tooltip.onbeforeopen">
                /// Raised just before the Tooltip appears.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                onbeforeopen: createEvent("beforeopen"),

                /// <field type="Function" locid="WinJS.UI.Tooltip.onopened" helpKeyword="WinJS.UI.Tooltip.onopened">
                /// Raised when the Tooltip is shown.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                onopened: createEvent("opened"),

                /// <field type="Function" locid="WinJS.UI.Tooltip.onbeforeclose" helpKeyword="WinJS.UI.Tooltip.onbeforeclose">
                /// Raised just before the Tooltip is hidden.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                onbeforeclose: createEvent("beforeclose"),

                /// <field type="Function" locid="WinJS.UI.Tooltip.onclosed" helpKeyword="WinJS.UI.Tooltip.onclosed">
                /// Raised when the Tooltip is no longer displayed.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                onclosed: createEvent("closed"),

                dispose: function () {
                    /// <signature helpKeyword="WinJS.UI.Tooltip.dispose">
                    /// <summary locid="WinJS.UI.Tooltip.dispose">
                    /// Disposes this Tooltip.
                    /// </summary>
                    /// <compatibleWith platform="Windows" minVersion="8.0"/>
                    /// </signature>
                    if (this._disposed) {
                        return;
                    }

                    this._disposed = true;
                    _Dispose.disposeSubTree(this.element);
                    for (var i = 0, len = this._eventListenerRemoveStack.length; i < len; i++) {
                        this._eventListenerRemoveStack[i]();
                    }
                    this._onDismiss();
                    var data = _ElementUtilities.data(this._anchorElement);
                    if (data) {
                        delete data.tooltip;
                    }
                },

                addEventListener: function (eventName, eventCallBack, capture) {
                    /// <signature helpKeyword="WinJS.UI.Tooltip.addEventListener">
                    /// <summary locid="WinJS.UI.Tooltip.addEventListener">
                    /// Registers an event handler for the specified event.
                    /// </summary>
                    /// <param name="eventName" type="String" locid="WinJS.UI.Tooltip.addEventListener_p:eventName">The name of the event.</param>
                    /// <param name="eventCallback" type="Function" locid="WinJS.UI.Tooltip.addEventListener_p:eventCallback">The event handler function to associate with this event.</param>
                    /// <param name="capture" type="Boolean" locid="WinJS.UI.Tooltip.addEventListener_p:capture">Set to true to register the event handler for the capturing phase; set to false to register for the bubbling phase.</param>
                    /// <compatibleWith platform="Windows" minVersion="8.0"/>
                    /// </signature>

                    if (this._anchorElement) {
                        this._anchorElement.addEventListener(eventName, eventCallBack, capture);

                        var that = this;
                        this._eventListenerRemoveStack.push(function () {
                            that._anchorElement.removeEventListener(eventName, eventCallBack, capture);
                        });
                    }
                },

                removeEventListener: function (eventName, eventCallBack, capture) {
                    /// <signature helpKeyword="WinJS.UI.Tooltip.removeEventListener">
                    /// <summary locid="WinJS.UI.Tooltip.removeEventListener">
                    /// Unregisters an event handler for the specified event.
                    /// </summary>
                    /// <param name="eventName" type="String" locid="WinJS.UI.Tooltip.removeEventListener:eventName">The name of the event.</param>
                    /// <param name="eventCallback" type="Function" locid="WinJS.UI.Tooltip.removeEventListener:eventCallback">The event handler function to remove.</param>
                    /// <param name="capture" type="Boolean" locid="WinJS.UI.Tooltip.removeEventListener:capture">Set to true to unregister the event handler for the capturing phase; otherwise, set to false to unregister the event handler for the bubbling phase.</param>
                    /// <compatibleWith platform="Windows" minVersion="8.0"/>
                    /// </signature>

                    if (this._anchorElement) {
                        this._anchorElement.removeEventListener(eventName, eventCallBack, capture);
                    }
                },

                open: function (type) {
                    /// <signature helpKeyword="WinJS.UI.Tooltip.open">
                    /// <summary locid="WinJS.UI.Tooltip.open">
                    /// Shows the Tooltip.
                    /// </summary>
                    /// <param name="type" type="String" locid="WinJS.UI.Tooltip.open_p:type">The type of tooltip to show: "touch", "mouseover", "mousedown", or "keyboard". The default value is "mousedown".</param>
                    /// <compatibleWith platform="Windows" minVersion="8.0"/>
                    /// </signature>

                    // Open takes precedence over other triggering events
                    // Once tooltip is opened using open(), it can only be closed by time out(mouseover or keyboard) or explicitly by close().
                    this._triggerByOpen = true;

                    if (type !== "touch" && type !== "mouseover" && type !== "mousedown" && type !== "keyboard") {
                        type = "default";
                    }

                    switch (type) {
                        case "touch":
                            this._onInvoke("touch", "never");
                            break;
                        case "mouseover":
                            this._onInvoke("mouse", "auto");
                            break;
                        case "keyboard":
                            this._onInvoke("keyboard", "auto");
                            break;
                        case "mousedown":
                        case "default":
                            this._onInvoke("nodelay", "never");
                            break;
                    }

                },

                close: function () {
                    /// <signature helpKeyword="WinJS.UI.Tooltip.close">
                    /// <summary locid="WinJS.UI.Tooltip.close">
                    /// Hids the Tooltip.
                    /// </summary>
                    /// <compatibleWith platform="Windows" minVersion="8.0"/>
                    /// </signature>

                    this._onDismiss();
                },

                _cleanUpDOM: function () {
                    if (this._domElement) {
                        _Dispose.disposeSubTree(this._domElement);
                        _Global.document.body.removeChild(this._domElement);
                        this._domElement = null;

                        _Global.document.body.removeChild(this._phantomDiv);
                        this._phantomDiv = null;
                    }
                },

                _createTooltipDOM: function () {
                    this._cleanUpDOM();

                    this._domElement = _Global.document.createElement("div");

                    var id = _ElementUtilities._uniqueID(this._domElement);
                    this._domElement.setAttribute("id", id);

                    // Set the direction of tooltip according to anchor element's
                    var computedStyle = _Global.document.defaultView.getComputedStyle(this._anchorElement, null);
                    var elemStyle = this._domElement.style;
                    elemStyle.direction = computedStyle.direction;
                    elemStyle.writingMode = computedStyle["writing-mode"]; // must use CSS name, not JS name

                    // Make the tooltip non-focusable
                    this._domElement.setAttribute("tabindex", -1);

                    // Set the aria tags for accessibility
                    this._domElement.setAttribute("role", "tooltip");
                    this._anchorElement.setAttribute("aria-describedby", id);

                    // Set the tooltip content
                    if (this._lastContentType === "element") { // Last update through contentElement option
                        this._domElement.appendChild(this._contentElement);
                    } else { // Last update through innerHTML option
                        this._domElement.innerHTML = this._innerHTML;
                    }

                    _Global.document.body.appendChild(this._domElement);
                    _ElementUtilities.addClass(this._domElement, msTooltip);

                    // In the event of user-assigned classes, add those too
                    if (this._extraClass) {
                        _ElementUtilities.addClass(this._domElement, this._extraClass);
                    }

                    // Create a phantom div on top of the tooltip div to block all interactions
                    this._phantomDiv = _Global.document.createElement("div");
                    this._phantomDiv.setAttribute("tabindex", -1);
                    _Global.document.body.appendChild(this._phantomDiv);
                    _ElementUtilities.addClass(this._phantomDiv, msTooltipPhantom);
                    var zIndex = _Global.document.defaultView.getComputedStyle(this._domElement, null).zIndex + 1;
                    this._phantomDiv.style.zIndex = zIndex;
                },

                _raiseEvent: function (type, eventProperties) {
                    if (this._anchorElement) {
                        var customEvent = _Global.document.createEvent("CustomEvent");
                        customEvent.initCustomEvent(type, false, false, eventProperties);
                        this._anchorElement.dispatchEvent(customEvent);
                    }
                },

                // Support for keyboard navigation
                _captureLastKeyBlurOrPointerOverEvent: function (event) {
                    this._lastKeyOrBlurEvent = this._currentKeyOrBlurEvent;
                    switch (event.type) {
                        case "keyup":
                            if (event.keyCode === Key.shift) {
                                this._currentKeyOrBlurEvent = null;
                            } else {
                                this._currentKeyOrBlurEvent = "keyboard";
                            }
                            break;
                        case "focusout":
                            //anchor element no longer in focus, clear up the stack
                            this._currentKeyOrBlurEvent = null;
                            break;
                        default:
                            break;

                    }
                },

                _registerEventToListener: function (element, eventType) {
                    var that = this;
                    var handler = function (event) {
                        that._captureLastKeyBlurOrPointerOverEvent(event);
                        that._handleEvent(event);
                    };
                    _ElementUtilities._addEventListener(element, eventType, handler, false);

                    this._eventListenerRemoveStack.push(function () {
                        _ElementUtilities._removeEventListener(element, eventType, handler, false);
                    });
                },

                _events: function () {
                    for (var eventType in EVENTS_INVOKE) {
                        this._registerEventToListener(this._anchorElement, eventType);
                    }
                    for (var eventType in EVENTS_UPDATE) {
                        this._registerEventToListener(this._anchorElement, eventType);
                    }
                    for (eventType in EVENTS_DISMISS) {
                        this._registerEventToListener(this._anchorElement, eventType);
                    }
                    this._registerEventToListener(this._anchorElement, "contextmenu");
                    this._registerEventToListener(this._anchorElement, "MSHoldVisual");
                },

                _handleEvent: function (event) {
                    var eventType = event._normalizedType || event.type;
                    if (!this._triggerByOpen) {
                        // If the anchor element has children, we should ignore events that are caused within the anchor element
                        // Please note that we are not using event.target here as in bubbling phases from the child, the event target
                        // is usually the child
                        if (eventType in EVENTS_BY_CHILD && _ElementUtilities.eventWithinElement(this._anchorElement, event)) {
                            return;
                        } else if (isInvokeEvent(eventType, event.pointerType)) {
                            if (event.pointerType === PT_TOUCH) {
                                if (!this._isShown) {
                                    this._showTrigger = "touch";
                                }
                                this._onInvoke("touch", "never", event);
                            } else if (this._skipMouseOver && event.pointerType === PT_MOUSE && eventType === "pointerover") {
                                // In browsers which use touch (instead of pointer) events, when the user taps their finger on
                                // an element which has a tooltip, we receive the following sequence of events:
                                //   - pointerdown (from touchstart; causes the Tooltip to show)
                                //   - pointerup (from touchend; causes the Tooltip to hide)
                                //   - pointerover (from mouseover; causes the Tooltip to show)
                                // At the end, the Tooltip should be hidden but instead it'll be shown due to mouseover coming
                                // after touchend. To avoid this problem, we use the _skipMouseOver flag to ignore the mouseover
                                // that follows touchend.
                                this._skipMouseOver = false;
                                return;
                            } else {
                                var type = eventType.substring(0, 3) === "key" ? "keyboard" : "mouse";
                                if (!this._isShown) {
                                    this._showTrigger = type;
                                }
                                this._onInvoke(type, "auto", event);
                            }
                        } else if (eventType in EVENTS_UPDATE) {
                            this._contactPoint = { x: event.clientX, y: event.clientY };
                        } else if (isDismissEvent(eventType, event.pointerType)) {
                            var eventTrigger;
                            if (event.pointerType === PT_TOUCH) {
                                if (eventType === "pointerup") {
                                    this._skipMouseOver = true;
                                    var that = this;
                                    _BaseUtils._yieldForEvents(function () {
                                        that._skipMouseOver = false;
                                    });
                                }
                                eventTrigger = "touch";
                            } else {
                                eventTrigger = eventType.substring(0, 3) === "key" ? "keyboard" : "mouse";
                            }
                            if (eventType !== "focusout" && eventTrigger !== this._showTrigger) {
                                return;
                            }
                            this._onDismiss();
                        } else if (eventType === "contextmenu" || eventType === "MSHoldVisual") {
                            event.preventDefault();
                        }
                    }
                },

                _onShowAnimationEnd: function () {
                    if (this._shouldDismiss || this._disposed) {
                        return;
                    }
                    this._raiseEvent("opened");
                    if (this._domElement) {
                        if (this._hideDelay !== "never") {
                            var that = this;
                            var delay = this._infotip ? Math.min(3 * messageDuration, HIDE_DELAY_MAX) : messageDuration;
                            this._hideDelayTimer = this._setTimeout(function () {
                                that._onDismiss();
                            }, delay);
                        }
                    }
                },

                _onHideAnimationEnd: function () {
                    _Global.document.body.removeEventListener("DOMNodeRemoved", this._removeTooltip, false);
                    this._cleanUpDOM();
                    // Once we remove the tooltip from the DOM, we should remove the aria tag from the anchor
                    if (this._anchorElement) {
                        this._anchorElement.removeAttribute("aria-describedby");
                    }
                    lastCloseTime = (new Date()).getTime();
                    this._triggerByOpen = false;
                    if (!this._disposed) {
                        this._raiseEvent("closed");
                    }
                },

                _decideOnDelay: function (type) {
                    var value;
                    this._useAnimation = true;

                    if (type === "nodelay") {
                        value = 0;
                        this._useAnimation = false;
                    } else {
                        var curTime = (new Date()).getTime();
                        // If the mouse is moved immediately from another anchor that has
                        // tooltip open, we should use a shorter delay
                        if (curTime - lastCloseTime <= RESHOW_THRESHOLD) {
                            if (type === "touch") {
                                value = this._infotip ? DELAY_RESHOW_INFOTIP_TOUCH : DELAY_RESHOW_NONINFOTIP_TOUCH;
                            } else {
                                value = this._infotip ? DELAY_RESHOW_INFOTIP_NONTOUCH : DELAY_RESHOW_NONINFOTIP_NONTOUCH;
                            }
                            this._useAnimation = false;
                        } else if (type === "touch") {
                            value = this._infotip ? DELAY_INITIAL_TOUCH_LONG : DELAY_INITIAL_TOUCH_SHORT;
                        } else {
                            value = this._infotip ? infoTooltipNonTouchShowDelay : nonInfoTooltipNonTouchShowDelay;
                        }
                    }
                    return value;
                },

                // This function returns the anchor element's position in the Window coordinates.
                _getAnchorPositionFromElementWindowCoord: function () {
                    var rect = this._anchorElement.getBoundingClientRect();

                    return {
                        x: rect.left,
                        y: rect.top,
                        width: rect.width,
                        height: rect.height
                    };
                },

                _getAnchorPositionFromPointerWindowCoord: function (contactPoint) {
                    return {
                        x: contactPoint.x,
                        y: contactPoint.y,
                        width: 1,
                        height: 1
                    };
                },

                _canPositionOnSide: function (placement, viewport, anchor, tip) {
                    var availWidth = 0, availHeight = 0;

                    switch (placement) {
                        case "top":
                            availWidth = tip.width + this._offset;
                            availHeight = anchor.y;
                            break;
                        case "bottom":
                            availWidth = tip.width + this._offset;
                            availHeight = viewport.height - anchor.y - anchor.height;
                            break;
                        case "left":
                            availWidth = anchor.x;
                            availHeight = tip.height + this._offset;
                            break;
                        case "right":
                            availWidth = viewport.width - anchor.x - anchor.width;
                            availHeight = tip.height + this._offset;
                            break;
                    }
                    return ((availWidth >= tip.width + this._offset) && (availHeight >= tip.height + this._offset));
                },

                _positionOnSide: function (placement, viewport, anchor, tip) {
                    var left = 0, top = 0;

                    switch (placement) {
                        case "top":
                        case "bottom":
                            // Align the tooltip to the anchor's center horizontally
                            left = anchor.x + anchor.width / 2 - tip.width / 2;

                            // If the left boundary is outside the window, set it to 0
                            // If the right boundary is outside the window, set it to align with the window right boundary
                            left = Math.min(Math.max(left, 0), viewport.width - tip.width - SAFETY_NET_GAP);

                            top = (placement === "top") ? anchor.y - tip.height - this._offset : anchor.y + anchor.height + this._offset;
                            break;
                        case "left":
                        case "right":
                            // Align the tooltip to the anchor's center vertically
                            top = anchor.y + anchor.height / 2 - tip.height / 2;

                            // If the top boundary is outside the window, set it to 0
                            // If the bottom boundary is outside the window, set it to align with the window bottom boundary
                            top = Math.min(Math.max(top, 0), viewport.height - tip.height - SAFETY_NET_GAP);

                            left = (placement === "left") ? anchor.x - tip.width - this._offset : anchor.x + anchor.width + this._offset;
                            break;
                    }

                    // Actually set the position
                    this._domElement.style.left = left + "px";
                    this._domElement.style.top = top + "px";

                    // Set the phantom's position and size
                    this._phantomDiv.style.left = left + "px";
                    this._phantomDiv.style.top = top + "px";
                    this._phantomDiv.style.width = tip.width + "px";
                    this._phantomDiv.style.height = tip.height + "px";
                },

                _position: function (contactType) {
                    var viewport = { width: 0, height: 0 };
                    var anchor = { x: 0, y: 0, width: 0, height: 0 };
                    var tip = { width: 0, height: 0 };

                    viewport.width = _Global.document.documentElement.clientWidth;
                    viewport.height = _Global.document.documentElement.clientHeight;
                    if (_Global.document.defaultView.getComputedStyle(_Global.document.body, null)["writing-mode"] === "tb-rl") {
                        viewport.width = _Global.document.documentElement.clientHeight;
                        viewport.height = _Global.document.documentElement.clientWidth;
                    }

                    if (this._contactPoint && (contactType === "touch" || contactType === "mouse")) {
                        anchor = this._getAnchorPositionFromPointerWindowCoord(this._contactPoint);
                    } else {
                        // keyboard or programmatic is relative to element
                        anchor = this._getAnchorPositionFromElementWindowCoord();
                    }
                    tip.width = this._domElement.offsetWidth;
                    tip.height = this._domElement.offsetHeight;
                    var fallback_order = {
                        "top": ["top", "bottom", "left", "right"],
                        "bottom": ["bottom", "top", "left", "right"],
                        "left": ["left", "right", "top", "bottom"],
                        "right": ["right", "left", "top", "bottom"]
                    };
                    if (isLeftHanded) {
                        fallback_order.top[2] = "right";
                        fallback_order.top[3] = "left";
                        fallback_order.bottom[2] = "right";
                        fallback_order.bottom[3] = "left";
                    }

                    // Try to position the tooltip according to the placement preference
                    // We use this order:
                    // 1. Try the preferred placement
                    // 2. Try the opposite placement
                    // 3. If the preferred placement is top or bottom, we should try left
                    // and right (or right and left if left handed)
                    // If the preferred placement is left or right, we should try top and bottom
                    var order = fallback_order[this._placement];
                    var length = order.length;
                    for (var i = 0; i < length; i++) {
                        if (i === length - 1 || this._canPositionOnSide(order[i], viewport, anchor, tip)) {
                            this._positionOnSide(order[i], viewport, anchor, tip);
                            break;
                        }
                    }
                    return order[i];
                },

                _showTooltip: function (contactType) {
                    // Give a chance to dismiss the tooltip before it starts to show
                    if (this._shouldDismiss) {
                        return;
                    }
                    this._isShown = true;
                    this._raiseEvent("beforeopen");

                    // If the anchor is not in the DOM tree, we don't create the tooltip
                    if (!_Global.document.body.contains(this._anchorElement)) {
                        return;
                    }
                    if (this._shouldDismiss) {
                        return;
                    }

                    // If the contentElement is set to null or innerHTML set to null or "", we should NOT show the tooltip
                    if (this._lastContentType === "element") { // Last update through contentElement option
                        if (!this._contentElement) {
                            this._isShown = false;
                            return;
                        }
                    } else { // Last update through innerHTML option
                        if (!this._innerHTML || this._innerHTML === "") {
                            this._isShown = false;
                            return;
                        }
                    }

                    var that = this;
                    this._removeTooltip = function (event) {
                        var current = that._anchorElement;
                        while (current) {
                            if (event.target === current) {
                                _Global.document.body.removeEventListener("DOMNodeRemoved", that._removeTooltip, false);
                                that._cleanUpDOM();
                                break;
                            }
                            current = current.parentNode;
                        }
                    };

                    _Global.document.body.addEventListener("DOMNodeRemoved", this._removeTooltip, false);
                    this._createTooltipDOM();
                    this._position(contactType);
                    if (this._useAnimation) {
                        Animations.fadeIn(this._domElement)
                            .then(this._onShowAnimationEnd.bind(this));
                    } else {
                        this._onShowAnimationEnd();
                    }
                },

                _onInvoke: function (type, hide, event) {
                    // Reset the dismiss flag
                    this._shouldDismiss = false;

                    // If the tooltip is already shown, ignore the current event
                    if (this._isShown) {
                        return;
                    }

                    // To handle keyboard support, we only want to display tooltip on the first tab key event only
                    if (event && event.type === "keyup") {
                        if (this._lastKeyOrBlurEvent === "keyboard" ||
                            !this._lastKeyOrBlurEvent && event.keyCode !== Key.tab) {
                            return;
                        }
                    }

                    // Set the hide delay,
                    this._hideDelay = hide;

                    this._contactPoint = null;
                    if (event) { // Open through interaction
                        this._contactPoint = { x: event.clientX, y: event.clientY };
                        // Tooltip display offset differently for touch events and non-touch events
                        if (type === "touch") {
                            this._offset = OFFSET_TOUCH;
                        } else if (type === "keyboard") {
                            this._offset = OFFSET_KEYBOARD;
                        } else {
                            this._offset = OFFSET_MOUSE;
                        }
                    } else { // Open Programmatically
                        if (type === "touch") {
                            this._offset = OFFSET_PROGRAMMATIC_TOUCH;
                        } else {
                            this._offset = OFFSET_PROGRAMMATIC_NONTOUCH;
                        }
                    }

                    this._clearTimeout(this._delayTimer);
                    this._clearTimeout(this._hideDelayTimer);

                    // Set the delay time
                    var delay = this._decideOnDelay(type);
                    if (delay > 0) {
                        var that = this;
                        this._delayTimer = this._setTimeout(function () {
                            that._showTooltip(type);
                        }, delay);
                    } else {
                        this._showTooltip(type);
                    }
                },

                _onDismiss: function () {
                    // Set the dismiss flag so that we don't miss dismiss events
                    this._shouldDismiss = true;

                    // If the tooltip is already dismissed, ignore the current event
                    if (!this._isShown) {
                        return;
                    }

                    this._isShown = false;

                    // Reset tooltip state
                    this._showTrigger = "mouse";

                    if (this._domElement) {
                        this._raiseEvent("beforeclose");
                        if (this._useAnimation) {
                            Animations.fadeOut(this._domElement)
                                .then(this._onHideAnimationEnd.bind(this));
                        } else {
                            this._onHideAnimationEnd();
                        }
                    } else {
                        this._raiseEvent("beforeclose");
                        this._raiseEvent("closed");
                    }
                },

                _setTimeout: function (callback, delay) {
                    return _Global.setTimeout(callback, delay);
                },

                _clearTimeout: function (id) {
                    _Global.clearTimeout(id);
                }
            }, {

                _DELAY_INITIAL_TOUCH_SHORT: {
                    get: function () { return DELAY_INITIAL_TOUCH_SHORT; },
                },

                _DELAY_INITIAL_TOUCH_LONG: {
                    get: function () { return DELAY_INITIAL_TOUCH_LONG ; }
                },

                _DEFAULT_MOUSE_HOVER_TIME: {
                    get: function () { return DEFAULT_MOUSE_HOVER_TIME; }
                },

                _DEFAULT_MESSAGE_DURATION: {
                    get: function () { return DEFAULT_MESSAGE_DURATION; }
                },

                _DELAY_RESHOW_NONINFOTIP_TOUCH: {
                    get: function () { return DELAY_RESHOW_NONINFOTIP_TOUCH; }
                },

                _DELAY_RESHOW_NONINFOTIP_NONTOUCH: {
                    get: function () { return DELAY_RESHOW_NONINFOTIP_NONTOUCH; }
                },

                _DELAY_RESHOW_INFOTIP_TOUCH: {
                    get: function () { return DELAY_RESHOW_INFOTIP_TOUCH; }
                },

                _DELAY_RESHOW_INFOTIP_NONTOUCH: {
                    get: function () { return DELAY_RESHOW_INFOTIP_NONTOUCH; }
                },

                _RESHOW_THRESHOLD: {
                    get: function () { return RESHOW_THRESHOLD; }
                },

                _HIDE_DELAY_MAX: {
                    get: function () { return HIDE_DELAY_MAX; }
                },
            });
        })
    });

});
// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/Rating',[
    '../Core/_Global',
    '../Core/_Base',
    '../Core/_ErrorFromName',
    '../Core/_Events',
    '../Core/_Resources',
    '../Utilities/_Control',
    '../Utilities/_ElementUtilities',
    '../Utilities/_Hoverable',
    '../Utilities/_SafeHtml',
    './Tooltip',
    'require-style!less/desktop/controls',
    'require-style!less/phone/controls'
    ], function ratingInit(_Global,_Base, _ErrorFromName, _Events, _Resources, _Control, _ElementUtilities, _Hoverable, _SafeHtml, Tooltip) {
    "use strict";

    // Rating control implementation
    _Base.Namespace.define("WinJS.UI", {
        /// <field>
        /// <summary locid="WinJS.UI.Rating">
        /// The Rating control allows users to give a number on a scale of 1 to maxRating (5 is the default).
        /// </summary>
        /// <compatibleWith platform="Windows" minVersion="8.0"/>
        /// </field>
        /// <icon src="ui_winjs.ui.rating.12x12.png" width="12" height="12" />
        /// <icon src="ui_winjs.ui.rating.16x16.png" width="16" height="16" />
        /// <htmlSnippet><![CDATA[<div data-win-control="WinJS.UI.Rating"></div>]]></htmlSnippet>
        /// <event name="previewchange" bubbles="false" locid="WinJS.UI.Rating_e:previewchange">Raised when the user chooses a new tentative rating but hasn't commited the change.</event>
        /// <event name="cancel" bubbles="false" locid="WinJS.UI.Rating_e:cancel">Raised when the user finishes interacting with the rating control without committing a tentative rating.</event>
        /// <event name="change" bubbles="false" locid="WinJS.UI.Rating_e:change">Raised when the user commits a change to the userRating.</event>
        /// <part name="rating" class="win-rating" locid="WinJS.UI.Rating_part:rating">The entire Rating control.</part>
        /// <part name="average-empty" class="win-star win-average win-empty" locid="WinJS.UI.Rating_part:average-empty">The empty star when the Rating control shows the average rating.</part>
        /// <part name="average-full" class="win-star win-average win-full" locid="WinJS.UI.Rating_part:average-full">The full star when the Rating control shows the average rating.</part>
        /// <part name="user-empty" class="win-star win-user win-empty" locid="WinJS.UI.Rating_part:user-empty">The empty star when the Rating control shows the user rating.</part>
        /// <part name="user-full" class="win-star win-user win-full" locid="WinJS.UI.Rating_part:user-full">The full star when the Rating control shows the user rating.</part>
        /// <part name="tentative-empty" class="win-star win-tentative win-empty" locid="WinJS.UI.Rating_part:tentative-empty">The empty star when the Rating control shows the tentative rating.</part>
        /// <part name="tentative-full" class="win-star win-tentative win-full" locid="WinJS.UI.Rating_part:tentative-full">The full star when the Rating control shows the tentative rating.</part>
        /// <part name="disabled-empty" class="win-star win-disabled win-empty" locid="WinJS.UI.Rating_part:disabled-empty">The empty star when the control is disabled.</part>
        /// <part name="disabled-full" class="win-star win-disabled win-full" locid="WinJS.UI.Rating_part:disabled-full">The full star when the control is disabled.</part>
        /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
        /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
        /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
        Rating: _Base.Namespace._lazy(function () {
            var createEvent = _Events._createEventProperty;

            var strings = {
                get averageRating() { return _Resources._getWinJSString("ui/averageRating").value; },
                get clearYourRating() { return _Resources._getWinJSString("ui/clearYourRating").value; },
                get tentativeRating() { return _Resources._getWinJSString("ui/tentativeRating").value; },
                get tooltipStringsIsInvalid() { return "Invalid argument: tooltipStrings must be null or an array of strings."; },
                get unrated() { return _Resources._getWinJSString("ui/unrated").value; },
                get userRating() { return _Resources._getWinJSString("ui/userRating").value; },
            };

            // Constants definition
            var DEFAULT_MAX_RATING = 5,
                DEFAULT_DISABLED = false,
                CANCEL = "cancel",
                CHANGE = "change",
                PREVIEW_CHANGE = "previewchange",
                MOUSE_LBUTTON = 0, // Event attribute to indicate a mouse left click
                PT_TOUCH = _ElementUtilities._MSPointerEvent.MSPOINTER_TYPE_TOUCH || "touch", // Pointer type to indicate a touch event
                PT_PEN = _ElementUtilities._MSPointerEvent.MSPOINTER_TYPE_PEN || "pen", // Pointer type to indicate a pen event
                PT_MOUSE = _ElementUtilities._MSPointerEvent.MSPOINTER_TYPE_MOUSE || "mouse"; // Pointer type to indicate a mouse event

            var hiddenAverageRatingCss = "padding-left: 0px; padding-right: 0px; border-left: 0px; border-right: 0px; -ms-flex: none; -webkit-flex: none; flex: none; display: none";

            // CSS class names
            var msRating = "win-rating",
                msRatingEmpty = "win-star win-empty",
                msRatingAverageEmpty = "win-star win-average win-empty",
                msRatingAverageFull = "win-star win-average win-full",
                msRatingUserEmpty = "win-star win-user win-empty",
                msRatingUserFull = "win-star win-user win-full",
                msRatingTentativeEmpty = "win-star win-tentative win-empty",
                msRatingTentativeFull = "win-star win-tentative win-full",
                msRatingDisabled = "win-disabled",
                msAverage = "win-average",
                msUser = "win-user";

            return _Base.Class.define(function Rating_ctor(element, options) {
                /// <signature helpKeyword="WinJS.UI.Rating.Rating">
                /// <summary locid="WinJS.UI.Rating.constructor">
                /// Creates a new Rating.
                /// </summary>
                /// <param name="element" domElement="true" locid="WinJS.UI.Rating.constructor_p:element">
                /// The DOM element that hosts the new Rating.
                /// </param>
                /// <param name="options" type="Object" locid="WinJS.UI.Rating.constructor_p:options">
                /// An object that contains one or more property/value pairs to apply to the new control.
                /// Each property of the options object corresponds to one of the control's properties or events.
                /// Event names must begin with "on". For example, to provide a handler for the cancel event,
                /// add a property named "oncancel" to the options object and set its value to the event handler.
                /// This parameter is optional.
                /// </param>
                /// <returns type="WinJS.UI.Rating" locid="WinJS.UI.Rating.constructor_returnValue">
                /// The new Rating.
                /// </returns>
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </signature>
                this._disposed = false;

                element = element || _Global.document.createElement("div");
                options = options || {};
                this._element = element;
                _ElementUtilities.addClass(this._element, "win-disposable");

                //initialize properties with default value
                this._userRating = 0;
                this._averageRating = 0;
                this._disabled = DEFAULT_DISABLED;
                this._enableClear = true;
                this._tooltipStrings = [];

                this._controlUpdateNeeded = false;
                this._setControlSize(options.maxRating);
                if (!options.tooltipStrings) {
                    this._updateTooltips(null);
                }
                _Control.setOptions(this, options);
                this._controlUpdateNeeded = true;
                this._forceLayout();

                // Register for notification when we are added to DOM
                _ElementUtilities._addInsertedNotifier(this._element);

                // Remember ourselves
                element.winControl = this;
                this._events();
            }, {
                /// <field type="Number" integer="true" locid="WinJS.UI.Rating.maxRating" helpKeyword="WinJS.UI.Rating.maxRating">
                /// Gets or sets the maximum possible rating value. The default is 5.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                maxRating: {
                    get: function () {
                        return this._maxRating;
                    },
                    set: function (value) {
                        this._setControlSize(value);
                        this._forceLayout();
                    }
                },

                /// <field type="Number" integer="true" locid="WinJS.UI.Rating.userRating" helpKeyword="WinJS.UI.Rating.userRating">
                /// Gets or sets the user's rating. This value must be between greater than or equal to zero and less than or equal to the maxRating.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                userRating: {
                    get: function () {
                        return this._userRating;
                    },
                    set: function (value) {
                        // Coerce value to a positive integer between 0 and maxRating
                        this._userRating = Math.max(0, Math.min(Number(value) >> 0, this._maxRating));
                        this._updateControl();
                    }
                },

                /// <field type="Number" locid="WinJS.UI.Rating.averageRating" helpKeyword="WinJS.UI.Rating.averageRating">
                /// Gets or sets the average rating as a float value. This value must be [equal to zero] OR [greater than or equal to 1 AND less than or equal to the maxRating].
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                averageRating: {
                    get: function () {
                        return this._averageRating;
                    },
                    set: function (value) {
                        // Coerce value to either 0 or a positive float between 1 and maxRating
                        this._averageRating = (Number(value) < 1) ? 0 : Math.min(Number(value) || 0, this._maxRating);
                        if (this._averageRatingElement) { // After the control has been created..
                            this._ensureAverageMSStarRating(); // ..ensure correct msStarRating is given to 'average-rating' star.
                        }
                        this._updateControl();
                    }
                },

                /// <field type="Boolean" locid="WinJS.UI.Rating.disabled" helpKeyword="WinJS.UI.Rating.disabled">
                /// Gets or sets a value that specifies whether the control is disabled. When the control is disabled, the user can't specify a
                /// new rating or modify an existing rating.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                disabled: {
                    get: function () {
                        return this._disabled;
                    },
                    set: function (value) {
                        this._disabled = !!value;
                        if (this._disabled) {
                            this._clearTooltips();
                        }
                        this._updateTabIndex();
                        this._updateControl();
                    }
                },

                /// <field type="Boolean" locid="WinJS.UI.Rating.enableClear" helpKeyword="WinJS.UI.Rating.enableClear">
                /// Gets or sets whether the control lets the user clear the rating.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                enableClear: {
                    get: function () {
                        return this._enableClear;
                    },
                    set: function (value) {
                        this._enableClear = !!value;
                        this._setAriaValueMin();
                        this._updateControl();
                    }
                },

                /// <field type="Array" locid="WinJS.UI.Rating.tooltipStrings" helpKeyword="WinJS.UI.Rating.tooltipStrings">
                /// Gets or sets a set of descriptions to show for rating values in the tooltip. The array must
                /// contain a string for each available rating value, and it can contain an optional string
                /// (at the end of the array) for the clear rating option.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                tooltipStrings: {
                    get: function () {
                        return this._tooltipStrings;
                    },
                    set: function (value) {
                        if (typeof value !== "object") {
                            throw new _ErrorFromName("WinJS.UI.Rating.TooltipStringsIsInvalid", strings.tooltipStringsIsInvalid);
                        }
                        this._updateTooltips(value);
                        this._updateAccessibilityRestState();
                    }
                },

                /// <field type="HTMLElement" domElement="true" hidden="true" locid="WinJS.UI.Rating.element" helpKeyword="WinJS.UI.Rating.element">
                /// Gets the DOM element that hosts the Rating.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                element: {
                    get: function () {
                        return this._element;
                    }
                },

                /// <field type="Function" locid="WinJS.UI.Rating.oncancel" helpKeyword="WinJS.UI.Rating.oncancel">
                /// Raised when the user finishes interacting with the rating control without committing a tentative rating.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                oncancel: createEvent(CANCEL),

                /// <field type="Function" locid="WinJS.UI.Rating.onchange" helpKeyword="WinJS.UI.Rating.onchange">
                /// Raised when the user commits a change to the userRating.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                onchange: createEvent(CHANGE),

                /// <field type="Function" locid="WinJS.UI.Rating.onpreviewchange" helpKeyword="WinJS.UI.Rating.onpreviewchange">
                /// Raised when the user is choosing a new tentative Rating.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                onpreviewchange: createEvent(PREVIEW_CHANGE),

                dispose: function () {
                    /// <signature helpKeyword="WinJS.UI.Rating.dispose">
                    /// <summary locid="WinJS.UI.Rating.dispose">
                    /// Disposes this Rating.
                    /// </summary>
                    /// <compatibleWith platform="Windows" minVersion="8.0"/>
                    /// </signature>
                    if (this._disposed) {
                        return;
                    }
                    this._disposed = true;

                    for (var i = 0; i < this._toolTips.length; i++) {
                        this._toolTips[i].dispose();
                    }
                    this._toolTips = null;
                },

                addEventListener: function (eventName, eventCallBack, capture) {
                    /// <signature helpKeyword="WinJS.UI.Rating.addEventListener">
                    /// <summary locid="WinJS.UI.Rating.addEventListener">
                    /// Registers an event handler for the specified event.
                    /// </summary>
                    /// <param name="eventName" type="String" locid="WinJS.UI.Rating.addEventListener_p:eventName">The name of the event.</param>
                    /// <param name="eventCallback" type="Function" locid="WinJS.UI.Rating.addEventListener_p:eventCallback">The event handler function to associate with this event.</param>
                    /// <param name="capture" type="Boolean" locid="WinJS.UI.Rating.addEventListener_p:capture">Set to true to register the event handler for the capturing phase; set to false to register for the bubbling phase.</param>
                    /// <compatibleWith platform="Windows" minVersion="8.0"/>
                    /// </signature>

                    this._element.addEventListener(eventName, eventCallBack, capture);
                },

                removeEventListener: function (eventName, eventCallBack, capture) {
                    /// <signature helpKeyword="WinJS.UI.Rating.removeEventListener">
                    /// <summary locid="WinJS.UI.Rating.removeEventListener">
                    /// Unregisters an event handler for the specified event.
                    /// </summary>
                    /// <param name="eventName" type="String" locid="WinJS.UI.Rating.removeEventListener_p:eventName">The name of the event.</param>
                    /// <param name="eventCallback" type="Function" locid="WinJS.UI.Rating.removeEventListener_p:eventCallback">The event handler function to remove.</param>
                    /// <param name="capture" type="Boolean" locid="WinJS.UI.Rating.removeEventListener_p:capture">Set to true to unregister the event handler for the capturing phase; otherwise, set to false to unregister the event handler for the bubbling phase.</param>
                    /// <compatibleWith platform="Windows" minVersion="8.0"/>
                    /// </signature>

                    return this._element.removeEventListener(eventName, eventCallBack, capture);
                },

                _forceLayout: function () {
                    if (!this._controlUpdateNeeded) {
                        return;
                    }

                    // Disable incremental update during redraw, postpone till all properties set
                    var updateNeeded = false;
                    this._updateControl = function () {
                        updateNeeded = true;
                    };

                    // Coerce userRating and averageRating to conform to maxRating
                    this.userRating = this._userRating;
                    this.averageRating = this._averageRating;

                    // Reset properties
                    this._lastEventWasChange = false;
                    this._lastEventWasCancel = false;
                    this._tentativeRating = -1;
                    this._captured = false;
                    this._pointerDownFocus = false;
                    this._elements = [];
                    this._toolTips = [];
                    this._clearElement = null;

                    // Element that is used for showing average rating
                    this._averageRatingElement = null;
                    this._elementWidth = null;
                    this._elementPadding = null;
                    this._elementBorder = null;
                    this._floatingValue = 0;

                    this._createControl();
                    this._setAccessibilityProperties();

                    delete this._updateControl;
                    if (updateNeeded) {
                        this._updateControl();
                    }

                },

                // Hide the help star if the control is not showing average rating
                _hideAverageRating: function () {
                    if (!this._averageRatingHidden) {
                        this._averageRatingHidden = true;
                        this._averageRatingElement.style.cssText = hiddenAverageRatingCss;
                    }
                },

                _createControl: function () {
                    // rating control could have more than one class name
                    _ElementUtilities.addClass(this._element, msRating);

                    var html = "";
                    this._averageRatingHidden = true;
                    // create control
                    for (var i = 0; i <= this._maxRating; i++) {
                        if (i === this._maxRating) {
                            html = html + "<div class='" + msRatingAverageFull + "' style='" + hiddenAverageRatingCss + "'></div>";
                        } else {
                            html = html + "<div class='" + msRatingUserEmpty + "'></div>";
                        }
                    }
                    _SafeHtml.setInnerHTMLUnsafe(this._element, html);
                    var oneStar = this._element.firstElementChild;
                    var i = 0;
                    while (oneStar) {
                        this._elements[i] = oneStar;
                        if (i < this._maxRating) {
                            _ElementUtilities.data(oneStar).msStarRating = i + 1;
                        }
                        oneStar = oneStar.nextElementSibling;
                        i++;
                    }
                    this._averageRatingElement = this._elements[this._maxRating];
                    this._ensureAverageMSStarRating();

                    // add focus capability relative to element's position in the document
                    this._updateTabIndex();
                },

                _setAriaValueMin: function () {
                    this._element.setAttribute("aria-valuemin", this._enableClear ? 0 : 1);
                },

                _setAccessibilityProperties: function () {
                    this._element.setAttribute("role", "slider");
                    this._element.setAttribute("aria-valuemax", this._maxRating);
                    this._setAriaValueMin();
                    this._updateAccessibilityRestState();
                },

                _getText: function (number) {
                    var string = this._tooltipStrings[number];
                    if (string) {
                        var tempDiv = _Global.document.createElement("div");
                        tempDiv.innerHTML = string;
                        return tempDiv.textContent;
                    } else if (number === this._maxRating) {
                        return strings.clearYourRating;
                    } else {
                        return number + 1;
                    }
                },

                _updateAccessibilityRestState: function () {
                    var element = this._element;
                    this._ariaValueNowMutationObserver && this._ariaValueNowMutationObserver.disconnect();
                    element.setAttribute("aria-readOnly", this._disabled);

                    if (this._userRating !== 0) {
                        element.setAttribute("aria-valuenow", this._userRating);
                        element.setAttribute("aria-label", strings.userRating);
                        element.setAttribute("aria-valuetext", this._getText(this._userRating - 1));
                    } else if (this._averageRating !== 0) {
                        element.setAttribute("aria-valuenow", this._averageRating);
                        element.setAttribute("aria-label", strings.averageRating);
                        element.setAttribute("aria-valuetext", this._averageRating);
                    } else {
                        element.setAttribute("aria-valuenow", strings.unrated);
                        element.setAttribute("aria-label", strings.userRating);
                        element.setAttribute("aria-valuetext", strings.unrated);
                    }

                    this._ariaValueNowMutationObserver && this._ariaValueNowMutationObserver.observe(this._element, { attributes: true, attributeFilter: ["aria-valuenow"] });
                },

                _updateAccessibilityHoverState: function () {
                    var element = this._element;
                    this._ariaValueNowMutationObserver && this._ariaValueNowMutationObserver.disconnect();
                    element.setAttribute("aria-readOnly", this._disabled);

                    if (this._tentativeRating > 0) {
                        element.setAttribute("aria-label", strings.tentativeRating);
                        element.setAttribute("aria-valuenow", this._tentativeRating);
                        element.setAttribute("aria-valuetext", this._getText(this._tentativeRating - 1));
                    } else if (this._tentativeRating === 0) {
                        element.setAttribute("aria-valuenow", strings.unrated);
                        element.setAttribute("aria-label", strings.tentativeRating);
                        element.setAttribute("aria-valuetext", this._getText(this._maxRating));
                    } else {
                        //shouldn't get here
                        element.setAttribute("aria-valuenow", strings.unrated);
                        element.setAttribute("aria-label", strings.tentativeRating);
                        element.setAttribute("aria-valuetext", strings.unrated);
                    }

                    this._ariaValueNowMutationObserver && this._ariaValueNowMutationObserver.observe(this._element, { attributes: true, attributeFilter: ["aria-valuenow"] });
                },

                _ensureTooltips: function () {
                    if (this.disabled) {
                        return;
                    }

                    if (this._toolTips.length === 0) {
                        for (var i = 0; i < this._maxRating; i++) {
                            this._toolTips[i] = new Tooltip.Tooltip(this._elements[i]);
                        }
                    }
                },

                // decrement tentative rating by one
                _decrementRating: function () {
                    this._closeTooltip();
                    var firePreviewChange = true;
                    if ((this._tentativeRating === 0) || ((this._tentativeRating === -1) && (this._userRating === 0))) {
                        firePreviewChange = false;
                    } else {
                        if (this._tentativeRating > 0) {
                            this._tentativeRating--;
                        } else if (this._tentativeRating === -1) {
                            if (this._userRating !== 0) {
                                if (this._userRating > 0) {
                                    this._tentativeRating = this._userRating - 1;
                                } else {
                                    this._tentativeRating = 0;
                                }
                            } else {
                                this._tentativeRating = 0;
                            }
                        }

                        if ((this._tentativeRating === 0) && !this._enableClear) {
                            this._tentativeRating = 1;
                            firePreviewChange = false;
                        }
                    }

                    this._showTentativeRating(firePreviewChange, "keyboard");
                },

                _events: function () {
                    var that = this;
                    function ratingHandler(eventName) {
                        return {
                            name: eventName,
                            lowerCaseName: eventName.toLowerCase(),
                            handler: function (event) {
                                var fn = that["_on" + eventName];
                                if (fn) {
                                    fn.apply(that, [event]);
                                }
                            }
                        };
                    }

                    var eventsRegisteredInLowerCase = [
                            ratingHandler("KeyDown"),
                            ratingHandler("FocusOut"),
                            ratingHandler("FocusIn"),
                            ratingHandler("PointerCancel"),
                            ratingHandler("PointerDown"),
                            ratingHandler("PointerMove"),
                            ratingHandler("PointerOver"),
                            ratingHandler("PointerUp"),
                            ratingHandler("PointerOut")
                    ];
                    var events = [
                        ratingHandler("WinJSNodeInserted")
                    ];

                    var i;
                    for (i = 0; i < eventsRegisteredInLowerCase.length; ++i) {
                        _ElementUtilities._addEventListener(this._element, eventsRegisteredInLowerCase[i].lowerCaseName, eventsRegisteredInLowerCase[i].handler, false);
                    }
                    for (i = 0; i < events.length; ++i) {
                        this._element.addEventListener(events[i].name, events[i].handler, false);
                    }

                    this._ariaValueNowMutationObserver = new _ElementUtilities._MutationObserver(this._ariaValueNowChanged.bind(this));
                    this._ariaValueNowMutationObserver.observe(this._element, { attributes: true, attributeFilter: ["aria-valuenow"] });
                },

                _onWinJSNodeInserted: function () {
                    this._recalculateStarProperties();
                    this._updateControl();
                },

                _recalculateStarProperties: function () {
                    var j = 0;
                    // If the average rating is 1 we do not have correct padding on the first star so we are reading it from the second star
                    // When we create average rating star we are creating it from 2 divs - stars. The first one is the average rating star the second one is the regular rating star.
                    // If the average rating is 1 we are creating that rating on the following way - The first part of star
                    // (without right padding, right border) is average rating star - the second part is regular star that does not have left padding and left border anymore
                    // (we set on 0 to create average rating star). In that situation the average rating star has correct left padding and left border.
                    if (this._averageRating === 1) {
                        j = 1;
                    }
                    var style = _Global.getComputedStyle(this._elements[j]);
                    this._elementWidth = style.width;
                    if (_Global.getComputedStyle(this._element).direction === "rtl") {
                        this._elementPadding = style.paddingRight;
                        this._elementBorder = style.borderRight;
                    } else {
                        this._elementPadding = style.paddingLeft;
                        this._elementBorder = style.borderLeft;
                    }
                },

                // Hide the help star if the control is not showing average rating
                _hideAverageStar: function () {
                    // check if this average rating control
                    if (this._averageRating !== 0) {
                        // hide the empty star
                        this._resetAverageStar(false);
                    }
                },

                // increase tentative rating by one
                _incrementRating: function () {
                    this._closeTooltip();
                    var firePreviewChange = true;
                    if ((this._tentativeRating === this._maxRating) || ((this._tentativeRating === -1) && (this._userRating === this._maxRating))) {
                        firePreviewChange = false;
                    }

                    if (this._tentativeRating !== -1) {
                        if (this._tentativeRating < this._maxRating) {
                            this._tentativeRating++;
                        }
                    } else {
                        if (this._userRating !== 0) {
                            if (this._userRating < this._maxRating) {
                                this._tentativeRating = this._userRating + 1;
                            } else {
                                this._tentativeRating = this._maxRating;
                            }
                        } else {
                            this._tentativeRating = 1;
                        }
                    }
                    this._showTentativeRating(firePreviewChange, "keyboard");
                },

                _ariaValueNowChanged: function () {
                    if (!this._disabled) {
                        var attrNode = this._element.getAttributeNode("aria-valuenow");
                        if (attrNode !== null) {
                            var value = Number(attrNode.nodeValue);
                            if (this.userRating !== value) {
                                this.userRating = value;
                                this._tentativeRating = this._userRating;
                                this._raiseEvent(CHANGE, this._userRating);
                            }
                        }
                    }
                },

                _onPointerCancel: function () {
                    this._showCurrentRating();
                    if (!this._lastEventWasChange) {
                        this._raiseEvent(CANCEL, null);
                    }
                    this._captured = false;
                },

                _onPointerDown: function (eventObject) {
                    if (eventObject.pointerType === PT_MOUSE && eventObject.button !== MOUSE_LBUTTON) {
                        return; // Ignore any mouse clicks that are not left clicks.
                    }
                    if (!this._captured) { // Rating Control does not support multi-touch, ignore mspointerdown messages if the control already has capture.
                        this._pointerDownAt = { x: eventObject.clientX, y: eventObject.clientY };
                        this._pointerDownFocus = true;
                        if (!this._disabled) {
                            // Only capture the event when active to support block panning
                            _ElementUtilities._setPointerCapture(this._element, eventObject.pointerId);
                            this._captured = true;

                            if (eventObject.pointerType === PT_TOUCH) {
                                this._tentativeRating = _ElementUtilities.data(eventObject.target).msStarRating || 0;
                                // change states for all stars
                                this._setStarClasses(msRatingTentativeFull, this._tentativeRating, msRatingTentativeEmpty);
                                this._hideAverageStar();
                                this._updateAccessibilityHoverState();
                                this._openTooltip("touch");
                                this._raiseEvent(PREVIEW_CHANGE, this._tentativeRating);
                            } else {
                                this._openTooltip("mousedown");
                            }
                        }
                    }
                },

                _onCapturedPointerMove: function (eventObject, tooltipType) {
                    // Manual hit-test because we capture the pointer
                    // If the pointer is already down, we use its information.
                    var pointerAt = this._pointerDownAt || { x: eventObject.clientX, y: eventObject.clientY };

                    var star;
                    var hit = _ElementUtilities._elementsFromPoint(eventObject.clientX, pointerAt.y);
                    if (hit) {
                        for (var i = 0, len = hit.length; i < len; i++) {
                            var item = hit[i];
                            if (item.getAttribute("role") === "tooltip") {
                                return;
                            }
                            if (_ElementUtilities.hasClass(item, "win-star")) {
                                star = item;
                                break;
                            }
                        }
                    }
                    var starNum;
                    if (star && (star.parentElement === this._element)) {
                        starNum = _ElementUtilities.data(star).msStarRating || 0;
                    } else {
                        var left = 0, right = this.maxRating;
                        if (_Global.getComputedStyle(this._element).direction === "rtl") {
                            left = right;
                            right = 0;
                        }
                        if (eventObject.clientX < pointerAt.x) {
                            starNum = left;
                        } else {
                            starNum = right;
                        }
                    }

                    var firePreviewChange = false;
                    var newTentativeRating = Math.min(Math.ceil(starNum), this._maxRating);
                    if ((newTentativeRating === 0) && !this._enableClear) {
                        newTentativeRating = 1;
                    }
                    if (newTentativeRating !== this._tentativeRating) {
                        this._closeTooltip();
                        firePreviewChange = true;
                    }

                    this._tentativeRating = newTentativeRating;
                    this._showTentativeRating(firePreviewChange, tooltipType);
                    eventObject.preventDefault();
                },

                _onPointerMove: function (eventObject) {
                    if (this._captured) {
                        if (eventObject.pointerType === PT_TOUCH) {
                            this._onCapturedPointerMove(eventObject, "touch");
                        } else {
                            this._onCapturedPointerMove(eventObject, "mousedown");
                        }
                    }
                },

                _onPointerOver: function (eventObject) {
                    if (!this._disabled && (eventObject.pointerType === PT_PEN || eventObject.pointerType === PT_MOUSE)) {
                        this._onCapturedPointerMove(eventObject, "mouseover");
                    }
                },

                _onPointerUp: function (eventObject) {
                    if (this._captured) {
                        _ElementUtilities._releasePointerCapture(this._element, eventObject.pointerId);
                        this._captured = false;
                        this._onUserRatingChanged();
                    }
                    this._pointerDownAt = null;
                },

                _onFocusOut: function () {
                    if (!this._captured) {
                        this._onUserRatingChanged();
                        if (!this._lastEventWasChange && !this._lastEventWasCancel) {
                            this._raiseEvent(CANCEL, null);
                        }
                    }
                },

                _onFocusIn: function () {
                    if (!this._pointerDownFocus) {
                        // if the control is read only don't hover stars
                        if (!this._disabled) {
                            // change states for all previous stars
                            // but only if user didnt vote
                            if (this._userRating === 0) {
                                for (var i = 0; i < this._maxRating; i++) {
                                    this._elements[i].className = msRatingTentativeEmpty;
                                }
                            }
                            // hide the help star
                            this._hideAverageStar();
                        }

                        if (this._userRating !== 0) {
                            this._raiseEvent(PREVIEW_CHANGE, this._userRating);
                        } else {
                            this._raiseEvent(PREVIEW_CHANGE, 0);
                        }
                        this._tentativeRating = this._userRating;
                    }
                    this._pointerDownFocus = false;
                },

                _onKeyDown: function (eventObject) {
                    var Key = _ElementUtilities.Key;
                    var keyCode = eventObject.keyCode;
                    var rtlString = _Global.getComputedStyle(this._element).direction;
                    var handled = true;
                    switch (keyCode) {
                        case Key.enter: // Enter
                            this._onUserRatingChanged();
                            break;
                        case Key.tab: //Tab
                            this._onUserRatingChanged();
                            handled = false;
                            break;
                        case Key.escape: // escape
                            this._showCurrentRating();

                            if (!this._lastEventWasChange) {
                                this._raiseEvent(CANCEL, null);
                            }

                            break;
                        case Key.leftArrow: // Arrow Left
                            if (rtlString === "rtl") {
                                this._incrementRating();
                            } else {
                                this._decrementRating();
                            }
                            break;
                        case Key.upArrow: // Arrow Up
                            this._incrementRating();
                            break;
                        case Key.rightArrow: // Arrow Right
                            if (rtlString === "rtl") {
                                this._decrementRating();
                            } else {
                                this._incrementRating();
                            }
                            break;
                        case Key.downArrow: // Arrow Down
                            this._decrementRating();
                            break;
                        default:
                            var number = 0;
                            if ((keyCode >= Key.num0) && (keyCode <= Key.num9)) {
                                number = Key.num0;
                            } else if ((keyCode >= Key.numPad0) && (keyCode <= Key.numPad9)) {
                                number = Key.numPad0;
                            }

                            if (number > 0) {
                                var firePreviewChange = false;
                                var newTentativeRating = Math.min(keyCode - number, this._maxRating);
                                if ((newTentativeRating === 0) && !this._enableClear) {
                                    newTentativeRating = 1;
                                }
                                if (newTentativeRating !== this._tentativeRating) {
                                    this._closeTooltip();
                                    firePreviewChange = true;
                                }
                                this._tentativeRating = newTentativeRating;
                                this._showTentativeRating(firePreviewChange, "keyboard");
                            } else {
                                handled = false;
                            }
                            break;
                    }

                    if (handled) {
                        eventObject.stopPropagation();
                        eventObject.preventDefault();
                    }
                },

                _onPointerOut: function (eventObject) {
                    if (!this._captured && !_ElementUtilities.eventWithinElement(this._element, eventObject)) {
                        this._showCurrentRating();
                        if (!this._lastEventWasChange) {
                            // only fire cancel event if we move out of the rating control, and if
                            // user did not change rating on the control
                            this._raiseEvent(CANCEL, null);
                        }
                    }
                },

                _onUserRatingChanged: function () {
                    if (!this._disabled) {
                        this._closeTooltip();
                        // Only submit a change event if the user has altered the rating control value via PREVIEWCHANGE event.
                        if (this._userRating !== this._tentativeRating && !this._lastEventWasCancel && !this._lastEventWasChange) {
                            this.userRating = this._tentativeRating;
                            this._raiseEvent(CHANGE, this._userRating);
                        } else {
                            this._updateControl();
                        }
                    }
                },

                _raiseEvent: function (eventName, tentativeRating) {
                    if (!this._disabled) {
                        this._lastEventWasChange = (eventName === CHANGE);
                        this._lastEventWasCancel = (eventName === CANCEL);
                        if (_Global.document.createEvent) {
                            var event = _Global.document.createEvent("CustomEvent");
                            event.initCustomEvent(eventName, false, false, { tentativeRating: tentativeRating });
                            this._element.dispatchEvent(event);
                        }
                    }
                },

                _resetNextElement: function (prevState) {
                    if (this._averageRatingElement.nextSibling !== null) {
                        _ElementUtilities._setFlexStyle(this._averageRatingElement.nextSibling, {grow: 1, shrink: 1});
                        var style = this._averageRatingElement.nextSibling.style;
                        var direction = _Global.getComputedStyle(this._element).direction;
                        if (prevState) {
                            if (direction === "rtl") {
                                direction = "ltr";
                            } else {
                                direction = "rtl";
                            }
                        }
                        if (direction === "rtl") {
                            style.paddingRight = this._elementPadding;
                            style.borderRight = this._elementBorder;
                            style.direction = "rtl";
                        } else {
                            style.paddingLeft = this._elementPadding;
                            style.borderLeft = this._elementBorder;
                            style.direction = "ltr";
                        }
                        style.backgroundPosition = "left";
                        style.backgroundSize = "100% 100%";
                        style.width = this._resizeStringValue(this._elementWidth, 1, style.width);
                    }
                },

                _resetAverageStar: function (prevState) {
                    this._resetNextElement(prevState);
                    this._hideAverageRating();
                },

                _resizeStringValue: function (string, factor, curString) {
                    var number = parseFloat(string);
                    if (isNaN(number)) {
                        if (curString !== null) {
                            return curString;
                        } else {
                            return string;
                        }
                    }
                    var unit = string.substring(number.toString(10).length);
                    number = number * factor;
                    return (number + unit);
                },

                _setControlSize: function (value) {
                    // Coerce value to a positive integer between 0 and maxRating
                    // if negative default to DEFAULT_MAX_RATING
                    var maxRating = (Number(value) || DEFAULT_MAX_RATING) >> 0;
                    this._maxRating = maxRating > 0 ? maxRating : DEFAULT_MAX_RATING;
                },

                _updateTooltips: function (value) {
                    var i, max = 0;
                    if (value !== null) {
                        max = ((value.length <= this._maxRating + 1) ? value.length : this._maxRating + 1);
                        for (i = 0; i < max; i++) {
                            this._tooltipStrings[i] = value[i];
                        }
                    } else {
                        for (i = 0; i < this._maxRating; i++) {
                            this._tooltipStrings[i] = i + 1;
                        }
                        this._tooltipStrings[this._maxRating] = strings.clearYourRating;
                    }
                },

                _updateTabIndex: function () {
                    this._element.tabIndex = (this._disabled ? "-1" : "0");
                },

                _setStarClasses: function (classNameBeforeThreshold, threshold, classNameAfterThreshold) {
                    for (var i = 0; i < this._maxRating; i++) {
                        if (i < threshold) {
                            this._elements[i].className = classNameBeforeThreshold;
                        } else {
                            this._elements[i].className = classNameAfterThreshold;
                        }
                    }
                },

                // Average rating star is created from 2 divs:
                // In the first div the glyph starts from the beginning in the direction of the control
                // In the second div the glyph starts from the beginning in the opposite direction
                // That way we are making the average star look like one glyph
                _updateAverageStar: function () {
                    var style = this._averageRatingElement.style;
                    var nextStyle = this._averageRatingElement.nextSibling.style;
                    if (_Global.getComputedStyle(this._element).direction === "rtl") {
                        style.backgroundPosition = "right";
                        style.paddingRight = this._elementPadding;
                        style.borderRight = this._elementBorder;
                        nextStyle.paddingRight = "0px";
                        nextStyle.borderRight = "0px";
                        nextStyle.direction = "ltr";
                    } else {
                        style.backgroundPosition = "left";
                        nextStyle.backgroundPosition = "right";
                        style.paddingLeft = this._elementPadding;
                        style.borderLeft = this._elementBorder;
                        nextStyle.paddingLeft = "0px";
                        nextStyle.borderLeft = "0px";
                        nextStyle.direction = "rtl";
                    }
                    _ElementUtilities._setFlexStyle(this._averageRatingElement, {grow: this._floatingValue, shrink: this._floatingValue});
                    style.width = this._resizeStringValue(this._elementWidth, this._floatingValue, style.width);
                    style.backgroundSize = (100 / this._floatingValue) + "% 100%";
                    style.display = _Global.getComputedStyle(this._averageRatingElement.nextSibling).display;
                    this._averageRatingHidden = false;
                    _ElementUtilities._setFlexStyle(this._averageRatingElement.nextSibling, {grow: 1 - this._floatingValue, shrink: 1 - this._floatingValue});
                    nextStyle.width = this._resizeStringValue(this._elementWidth, 1 - this._floatingValue, nextStyle.width);
                    nextStyle.backgroundSize = (100 / (1 - this._floatingValue)) + "% 100%";
                },

                // show current rating
                _showCurrentRating: function () {
                    this._closeTooltip();
                    // reset tentative rating
                    this._tentativeRating = -1;
                    // if the control is read only then we didn't change anything on hover
                    if (!this._disabled) {
                        this._updateControl();
                    }
                    this._updateAccessibilityRestState();
                },

                _showTentativeRating: function (firePreviewChange, tooltipType) {
                    // if the control is read only don't hover stars
                    if ((!this._disabled) && (this._tentativeRating >= 0)) {
                        this._setStarClasses(msRatingTentativeFull, this._tentativeRating, msRatingTentativeEmpty);

                        // hide the empty star
                        this._hideAverageStar();
                    }

                    this._updateAccessibilityHoverState();

                    if (firePreviewChange) {
                        this._openTooltip(tooltipType);
                        this._raiseEvent(PREVIEW_CHANGE, this._tentativeRating);
                    }
                },

                _openTooltip: function (tooltipType) {
                    if (this.disabled) {
                        return;
                    }

                    this._ensureTooltips();
                    if (this._tentativeRating > 0) {
                        this._toolTips[this._tentativeRating - 1].innerHTML = this._tooltipStrings[this._tentativeRating - 1];
                        this._toolTips[this._tentativeRating - 1].open(tooltipType);
                    } else if (this._tentativeRating === 0) {
                        this._clearElement = _Global.document.createElement("div");
                        var distance = this._elements[0].offsetWidth + parseInt(this._elementPadding, 10);
                        if (_Global.getComputedStyle(this._element).direction === "ltr") {
                            distance *= -1;
                        }
                        this._clearElement.style.cssText = "visiblity:hidden; position:absolute; width:0px; height:100%; left:" + distance + "px; top:0px;";
                        this._elements[0].appendChild(this._clearElement);
                        this._toolTips[this._maxRating] = new Tooltip.Tooltip(this._clearElement);
                        this._toolTips[this._maxRating].innerHTML = this._tooltipStrings[this._maxRating];
                        this._toolTips[this._maxRating].open(tooltipType);
                    }
                },

                _closeTooltip: function () {
                    if (this._toolTips.length !== 0) {
                        if (this._tentativeRating > 0) {
                            this._toolTips[this._tentativeRating - 1].close();
                        } else if (this._tentativeRating === 0) {
                            if (this._clearElement !== null) {
                                this._toolTips[this._maxRating].close();
                                this._elements[0].removeChild(this._clearElement);
                                this._clearElement = null;
                            }
                        }
                    }
                },

                _clearTooltips: function () {
                    if (this._toolTips && this._toolTips.length !== 0) {
                        for (var i = 0; i < this._maxRating; i++) {
                            this._toolTips[i].innerHTML = null;
                        }
                    }
                },

                _appendClass: function (classNameToBeAdded) {
                    for (var i = 0; i <= this._maxRating; i++) {
                        _ElementUtilities.addClass(this._elements[i], classNameToBeAdded);
                    }
                },

                _setClasses: function (classNameBeforeThreshold, threshold, classNameAfterThreshold) {
                    for (var i = 0; i < this._maxRating; i++) {
                        if (i < threshold) {
                            this._elements[i].className = classNameBeforeThreshold;
                        } else {
                            this._elements[i].className = classNameAfterThreshold;
                        }
                    }
                },

                _ensureAverageMSStarRating: function () {
                    _ElementUtilities.data(this._averageRatingElement).msStarRating = Math.ceil(this._averageRating);
                },

                _updateControl: function () {
                    if (!this._controlUpdateNeeded) {
                        return;
                    }

                    // check for average rating (if user rating is specified then we are not showing average rating)
                    if ((this._averageRating !== 0) && (this._userRating === 0)) {
                        if ((this._averageRating >= 1) && (this._averageRating <= this._maxRating)) { // Display average rating
                            this._setClasses(msRatingAverageFull, this._averageRating - 1, msRatingAverageEmpty);
                            this._averageRatingElement.className = msRatingAverageFull;

                            for (var i = 0; i < this._maxRating; i++) {
                                // check if it is average star
                                if ((i < this._averageRating) && ((i + 1) >= this._averageRating)) {
                                    this._resetNextElement(false);

                                    this._element.insertBefore(this._averageRatingElement, this._elements[i]);

                                    this._floatingValue = this._averageRating - i;
                                    var elementStyle = _Global.getComputedStyle(this._elements[i]);
                                    this._elementWidth = elementStyle.width;

                                    if (_Global.getComputedStyle(this._element).direction === "rtl") {
                                        this._elementPadding = elementStyle.paddingRight;
                                        this._elementBorder = elementStyle.borderRight;
                                    } else {
                                        this._elementPadding = elementStyle.paddingLeft;
                                        this._elementBorder = elementStyle.borderLeft;
                                    }

                                    this._updateAverageStar();
                                }
                            }
                        }
                    }

                    // check if it is user rating control
                    if (this._userRating !== 0) {
                        if ((this._userRating >= 1) && (this._userRating <= this._maxRating)) { // Display user rating.
                            this._setClasses(msRatingUserFull, this._userRating, msRatingUserEmpty);

                            // hide average star
                            this._resetAverageStar(false);
                        }
                    }

                    // update stars if the rating is not set
                    if ((this._userRating === 0) && (this._averageRating === 0)) { // Display empty rating
                        this._setClasses(msRatingEmpty, this._maxRating);

                        // hide average star
                        this._resetAverageStar(false);
                    }

                    if (this.disabled) { // Display disabled rating.
                        this._appendClass(msRatingDisabled);
                    }

                    // update classes to differentiate average rating vs user rating
                    // If the userRating is 0 and averageRating is 0 we would like to treat that rating control as user rating control (not as average rating control).
                    if ((this._averageRating !== 0) && (this._userRating === 0)) {
                        this._appendClass(msAverage);
                    } else {
                        this._appendClass(msUser);
                    }

                    this._updateAccessibilityRestState();
                }
            });
        })
    });

});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/ToggleSwitch',[
    '../Core/_Global',
    '../Core/_Base',
    '../Core/_BaseUtils',
    '../Core/_Events',
    '../Core/_Resources',
    '../Utilities/_Control',
    '../Utilities/_ElementUtilities',
    'require-style!less/desktop/controls',
    'require-style!less/phone/controls'
    ],
    function toggleInit(_Global, _Base, _BaseUtils, _Events, _Resources, _Control, _ElementUtilities) {
        "use strict";

        _Base.Namespace.define("WinJS.UI", {
            /// <field>
            /// <summary locid="WinJS.UI.ToggleSwitch">
            /// A control that lets the user switch an option on or off.
            /// </summary>
            /// </field>
            /// <icon src="ui_winjs.ui.toggleswitch.12x12.png" width="12" height="12" />
            /// <icon src="ui_winjs.ui.toggleswitch.16x16.png" width="16" height="16" />
            /// <htmlSnippet><![CDATA[<div data-win-control="WinJS.UI.ToggleSwitch"></div>]]></htmlSnippet>
            /// <event name="change" bubbles="true" locid="WinJS.UI.ToggleSwitch_e:change">Raised when the switch is flipped to on (checked is set to true) or off (checked is set to false). </event>
            /// <part name="toggle" class="win-toggleswitch" locid="WinJS.UI.ToggleSwitch_part:toggle">The entire ToggleSwitch control.</part>
            /// <part name="track" class="win-toggleswitch-track" locid="WinJS.UI.ToggleSwitch_part:track">The slider portion of the toggle.</part>
            /// <part name="lower-fill" class="win-toggleswitch-fill-lower" locid="WinJS.UI.ToggleSwitch_part:fill-lower">The lower fill of the slider.</part>
            /// <part name="upper-fill" class="win-toggleswitch-fill-upper" locid="WinJS.UI.ToggleSwitch_part:fill-upper">The upper fill of the slider.</part>
            /// <part name="thumb" class="win-toggleswitch-thumb" locid="WinJS.UI.ToggleSwitch_part:thumb">The thumb of the slider.</part>
            /// <part name="title" class="win-toggleswitch-header" locid="WinJS.UI.ToggleSwitch_part:title">The main text for the ToggleSwitch control.</part>
            /// <part name="label-on" class="win-toggleswitch-value" locid="WinJS.UI.ToggleSwitch_part:label-on">The text for when the switch is on.</part>
            /// <part name="label-off" class="win-toggleswitch-value" locid="WinJS.UI.ToggleSwitch_part:label-off:">The text for when the switch is off.</part>
            /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
            /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
            /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
            ToggleSwitch: _Base.Namespace._lazy(function () {

                // Store some class names
                var classContainer = 'win-toggleswitch';
                var classHeader = 'win-toggleswitch-header';
                var classClick = 'win-toggleswitch-clickregion';
                var classTrack = 'win-toggleswitch-track';
                var classFill = 'win-toggleswitch-fill';
                var classFillLower = 'win-toggleswitch-fill-lower';
                var classFillUpper = 'win-toggleswitch-fill-upper';
                var classThumb = 'win-toggleswitch-thumb';
                var classValues = 'win-toggleswitch-values';
                var classValue = 'win-toggleswitch-value';
                var classValueOn = 'win-toggleswitch-value-on';
                var classValueOff = 'win-toggleswitch-value-off';
                var classDescription = 'win-toggleswitch-description';
                var classOn = 'win-toggleswitch-on';
                var classOff = 'win-toggleswitch-off';
                var classDisabled = 'win-toggleswitch-disabled';
                var classEnabled = 'win-toggleswitch-enabled';
                var classDragging = 'win-toggleswitch-dragging';
                var classPressed = 'win-toggleswitch-pressed';

                // Localized  strings
                var strings = {
                    get on() {
                        return _Resources._getWinJSString("ui/on").value;
                    },
                    get off() {
                        return _Resources._getWinJSString("ui/off").value;
                    },
                };

                // Define the ToggleSwitch class
                var Toggle = _Base.Class.define(function ToggleSwitch_ctor(element, options) {
                    /// <signature helpKeyword="WinJS.UI.ToggleSwitch.ToggleSwitch">
                    /// <summary locid="WinJS.UI.ToggleSwitch.constructor">
                    /// Creates a new ToggleSwitch.
                    /// </summary>
                    /// <param name="element" domElement="true" locid="WinJS.UI.ToggleSwitch.constructor_p:element">
                    /// The DOM element that hosts the ToggleSwitch.
                    /// </param>
                    /// <param name="options" type="Object" locid="WinJS.UI.ToggleSwitch.constructor_p:options">
                    /// An object that contains one or more property/value pairs to apply to the new control.
                    /// Each property of the options object corresponds to one of the control's properties or events.
                    /// Event names must begin with "on". For example, to provide a handler for the change event,
                    /// add a property named "onchange" to the options object and set its value to the event handler.
                    /// This parameter is optional.
                    /// </param>
                    /// <returns type="WinJS.UI.ToggleSwitch" locid="WinJS.UI.ToggleSwitch.constructor_returnValue">
                    /// The new ToggleSwitch.
                    /// </returns>
                    /// </signature>

                    // Main container
                    element = element || _Global.document.createElement('div');
                    this._domElement = element;
                    _ElementUtilities.addClass(this._domElement, classContainer);

                    // Set up DOM elements
                    this._domElement.innerHTML = [
                        '<div class="' + classHeader + '"></div>',
                        '<div class="' + classValues + '">',
                        '   <div class="' + classValue + ' ' + classValueOn + '"></div>',
                        '   <div class="' + classValue + ' ' + classValueOff + '"></div>',
                        '</div>',
                        '<div class="' + classClick + '">',
                        '   <div class="' + classTrack + '">',
                        '       <div class="' + classFill + ' ' + classFillLower + '"></div>',
                        '       <div class="' + classThumb + '"></div>',
                        '       <div class="' + classFill + ' ' + classFillUpper + '"></div>',
                        '   </div>',
                        '</div>',
                        '<div class="' + classDescription + '"></div>'
                    ].join('\n');

                    // Get references to elements
                    this._headerElement = this._domElement.firstElementChild;
                    this._labelsElement = this._headerElement.nextElementSibling;
                    this._labelOnElement = this._labelsElement.firstElementChild;
                    this._labelOffElement = this._labelOnElement.nextElementSibling;
                    this._clickElement = this._labelsElement.nextElementSibling;
                    this._trackElement = this._clickElement.firstElementChild;
                    this._fillLowerElement = this._trackElement.firstElementChild;
                    this._thumbElement = this._fillLowerElement.nextElementSibling;
                    this._fillUpperElement = this._thumbElement.nextElementSibling;
                    this._descriptionElement = this._clickElement.nextElementSibling;

                    // Set aria label info
                    this._headerElement.setAttribute('aria-hidden', true);
                    this._labelsElement.setAttribute('aria-hidden', true);
                    this._headerElement.setAttribute('id', _ElementUtilities._uniqueID(this._headerElement));
                    this._domElement.setAttribute('aria-labelledby', this._headerElement.id);
                    this._domElement.setAttribute('role', 'checkbox');

                    // Some initialization of main element
                    this._domElement.winControl = this;
                    _ElementUtilities.addClass(this._domElement, 'win-disposable');

                    // Add listeners
                    this._domElement.addEventListener('keydown', this._keyDownHandler.bind(this));
                    _ElementUtilities._addEventListener(this._clickElement, 'pointerdown', this._pointerDownHandler.bind(this));
                    _ElementUtilities._globalListener.addEventListener(this._domElement, 'pointermove', this._pointerMoveHandler.bind(this));
                    _ElementUtilities._globalListener.addEventListener(this._domElement, 'pointerup', this._pointerUpHandler.bind(this));

                    // Need mutation observer to listen for aria checked change
                    this._mutationObserver = new _ElementUtilities._MutationObserver(this._ariaChangedHandler.bind(this));
                    this._mutationObserver.observe(this._domElement, {attributes: true, attributeFilter: ['aria-checked']});

                    // Current x coord while being dragged
                    this._dragX = 0;
                    this._dragging = false;

                    // Default state
                    this.checked = false;
                    this.disabled = false;
                    this.labelOn = strings.on;
                    this.labelOff = strings.off;

                    // Apply options
                    _Control.setOptions(this, options);
                }, {
                    // Properties

                    /// <field type='HTMLElement' domElement='true' hidden='true' locid="WinJS.UI.ToggleSwitch.element" helpKeyword="WinJS.UI.ToggleSwitch.element">
                    /// The DOM element that hosts the ToggleSwitch control.
                    /// </field>
                    element: {
                        get: function () {
                            return this._domElement;
                        }
                    },
                    /// <field type="Boolean" locid="WinJS.UI.ToggleSwitch.checked" helpKeyword="WinJS.UI.ToggleSwitch.checked">
                    /// Gets or sets whether the control is on (checked is set to true) or off (checked is set to false).
                    /// </field>
                    checked: {
                        get: function () {
                            return this._checked;
                        },
                        set: function (value) {
                            value = !!value;
                            if (value === this.checked) {
                                return;
                            }

                            this._checked = value;
                            this._domElement.setAttribute('aria-checked', value);
                            if (value) {
                                _ElementUtilities.addClass(this._domElement, classOn);
                                _ElementUtilities.removeClass(this._domElement, classOff);
                            } else {
                                _ElementUtilities.addClass(this._domElement, classOff);
                                _ElementUtilities.removeClass(this._domElement, classOn);
                            }
                            this.dispatchEvent("change");
                        }
                    },
                    /// <field type="Boolean" locid="WinJS.UI.ToggleSwitch.disabled" helpKeyword="WinJS.UI.ToggleSwitch.disabled">
                    /// Gets or sets a value that specifies whether the control is disabled.
                    /// </field>
                    disabled: {
                        get: function () {
                            return this._disabled;
                        },
                        set: function (value) {
                            value = !!value;
                            if (value === this._disabled) {
                                return;
                            }

                            if (value) {
                                _ElementUtilities.addClass(this._domElement, classDisabled);
                                _ElementUtilities.removeClass(this._domElement, classEnabled);
                            } else {
                                _ElementUtilities.removeClass(this._domElement, classDisabled);
                                _ElementUtilities.addClass(this._domElement, classEnabled);
                            }

                            this._disabled = value;
                            this._domElement.setAttribute('aria-disabled', value);
                            this._domElement.setAttribute('tabIndex', value ? -1 : 0);
                        }
                    },
                    /// <field type="String" locid="WinJS.UI.ToggleSwitch.labelOn" helpKeyword="WinJS.UI.ToggleSwitch.labelOn">
                    /// Gets or sets the text that displays when the control is on (checked is set to true). The default value is "On".
                    /// </field>
                    labelOn: {
                        get: function () {
                            return this._labelOnElement.innerHTML;
                        },
                        set: function (value) {
                            this._labelOnElement.innerHTML = value;
                        }
                    },
                    /// <field type="String" locid="WinJS.UI.ToggleSwitch.labelOff" helpKeyword="WinJS.UI.ToggleSwitch.labelOff">
                    /// Gets or sets the text that displays when the control is off (checked is set to false). The default value is "Off".
                    /// </field>
                    labelOff: {
                        get: function () {
                            return this._labelOffElement.innerHTML;
                        },
                        set: function (value) {
                            this._labelOffElement.innerHTML = value;
                        }
                    },
                    /// <field type='String' locid="WinJS.UI.ToggleSwitch.title" helpKeyword="WinJS.UI.ToggleSwitch.title">
                    /// Gets or sets the main text for the ToggleSwitch control. This text is always displayed, regardless of whether
                    /// the control is switched on or off.
                    /// </field>
                    title: {
                        get: function () {
                            return this._headerElement.innerHTML;
                        },
                        set: function (value) {
                            this._headerElement.innerHTML = value;
                        }
                    },

                    // Events

                    /// <field type="Function" locid="WinJS.UI.ToggleSwitch.onchange" helpKeyword="WinJS.UI.ToggleSwitch.onchange">
                    /// Occurs when the ToggleSwitch control is flipped to on (checked == true) or off (checked == false).
                    /// </field>
                    onchange: _Events._createEventProperty('change'),

                    // Public methods
                    dispose: function ToggleSwitch_dispose() {
                        if (this._disposed) {
                            return;
                        }

                        this._disposed = true;
                    },

                    // Private event handlers
                    _ariaChangedHandler: function ToggleSwitch_ariaChanged() {
                        var value = this._domElement.getAttribute('aria-checked');
                        value = (value === 'true') ? true : false;
                        this.checked = value;
                    },
                    _keyDownHandler: function ToggleSwitch_keyDown(e) {
                        if (this.disabled) {
                            return;
                        }

                        // Toggle checked on spacebar
                        if (e.keyCode === _ElementUtilities.Key.space) {
                            this.checked = !this.checked;
                        }

                        // Arrow keys set value
                        if (e.keyCode === _ElementUtilities.Key.rightArrow ||
                            e.keyCode === _ElementUtilities.Key.upArrow) {
                            this.checked = true;
                        }
                        if (e.keyCode === _ElementUtilities.Key.leftArrow ||
                            e.keyCode === _ElementUtilities.Key.downArrow) {
                            this.checked = false;
                        }

                    },
                    _pointerDownHandler: function ToggleSwitch_pointerDown(e) {
                        if (this.disabled) {
                            return;
                        }

                        e.preventDefault();

                        this._mousedown = true;
                        this._dragXStart = e.pageX - this._trackElement.offsetLeft - this._thumbElement.offsetWidth / 2;
                        this._dragX = this._dragXStart;
                        this._dragging = false;
                        _ElementUtilities.addClass(this._domElement, classPressed);
                    },
                    _pointerUpHandler: function ToggleSwitch_pointerUp(e) {
                        if (this.disabled) {
                            return;
                        }

                        // Since up is a global event we should only take action
                        // if a mousedown was registered on us initially
                        if (!this._mousedown) {
                            return;
                        }

                        e = e.detail.originalEvent;
                        e.preventDefault();

                        // If the thumb is being dragged, pick a new value based on what the thumb
                        // was closest to
                        var isRTL = _Global.getComputedStyle(this._domElement).direction === 'rtl';
                        if (this._dragging) {
                            var maxX = this._trackElement.offsetWidth - this._thumbElement.offsetWidth;
                            this.checked = isRTL ? this._dragX < maxX / 2 : this._dragX >= maxX / 2;
                            this._dragging = false;
                            _ElementUtilities.removeClass(this._domElement, classDragging);
                        } else {
                            // Otherwise, just toggle the value as the up constitutes a
                            // click event
                            this.checked = !this.checked;
                        }

                        // Reset tracking variables and intermediate styles
                        this._mousedown = false;
                        this._thumbElement.style.left = '';
                        this._fillLowerElement.style.width = '';
                        this._fillUpperElement.style.width = '';
                        _ElementUtilities.removeClass(this._domElement, classPressed);
                    },
                    _pointerMoveHandler: function ToggleSwitch_pointerMove(e) {
                        if (this.disabled) {
                            return;
                        }

                        // Not dragging if mouse isn't down
                        if (!this._mousedown) {
                            return;
                        }

                        e = e.detail.originalEvent;
                        e.preventDefault();

                        // Get pointer x coord relative to control
                        var localMouseX = e.pageX - this._trackElement.offsetLeft - this._thumbElement.offsetWidth / 2;

                        // Calculate a new width for the fill elements and position for
                        // the thumb
                        var maxX = this._trackElement.offsetWidth - this._thumbElement.offsetWidth;
                        var trackOffset = this._fillLowerElement.offsetLeft + this._trackElement.clientLeft;
                        this._dragX = Math.min(maxX, localMouseX);
                        this._dragX = Math.max(0, this._dragX);

                        // Calculate if this pointermove constitutes switching to drag mode
                        if (!this._dragging && Math.abs(this._dragX - this._dragXStart) > 3) {
                            this._dragging = true;
                            _ElementUtilities.addClass(this._domElement, classDragging);
                        }

                        this._thumbElement.style.left = this._dragX + 'px';
                        this._fillLowerElement.style.width = (this._dragX - trackOffset) + 'px';
                        this._fillUpperElement.style.width = (maxX - this._dragX - trackOffset) + 'px';
                    }
                });

                // addEventListener, removeEventListener, dispatchEvent
                _Base.Class.mix(Toggle, _Control.DOMEventMixin);

                return Toggle;
            })
        });
    }
);
// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Semantic Zoom control
define('WinJS/Controls/SemanticZoom',[
    '../Core/_Global',
    '../Core/_Base',
    '../Core/_BaseUtils',
    '../Core/_ErrorFromName',
    '../Core/_Events',
    '../Core/_Resources',
    '../Core/_WriteProfilerMark',
    '../Animations',
    '../Animations/_TransitionAnimation',
    '../ControlProcessor',
    '../Promise',
    '../Utilities/_Control',
    '../Utilities/_Dispose',
    '../Utilities/_ElementUtilities',
    '../Utilities/_ElementListUtilities',
    '../Utilities/_Hoverable',
    'require-style!less/desktop/controls',
    'require-style!less/phone/controls'
    ], function semanticZoomInit(_Global, _Base, _BaseUtils, _ErrorFromName, _Events, _Resources, _WriteProfilerMark, Animations, _TransitionAnimation, ControlProcessor, Promise, _Control, _Dispose, _ElementUtilities, _ElementListUtilities, _Hoverable) {
    "use strict";

    _Base.Namespace.define("WinJS.UI", {
        /// <field>
        /// <summary locid="WinJS.UI.SemanticZoom">
        /// Enables the user to zoom between two different views supplied by two child controls.
        /// One child control supplies the zoomed-out view and the other provides the zoomed-in view.
        /// </summary>
        /// </field>
        /// <icon src="ui_winjs.ui.semanticzoom.12x12.png" width="12" height="12" />
        /// <icon src="ui_winjs.ui.semanticzoom.16x16.png" width="16" height="16" />
        /// <htmlSnippet supportsContent="true"><![CDATA[<div data-win-control="WinJS.UI.SemanticZoom"><div class="zoomedInContainer" data-win-control="WinJS.UI.ListView"></div><div class="zoomedOutContainer" data-win-control="WinJS.UI.ListView"></div></div>]]></htmlSnippet>
        /// <part name="semanticZoom" class="win-semanticzoom" locid="WinJS.UI.SemanticZoom_part:semanticZoom">The entire SemanticZoom control.</part>
        /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
        /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
        /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
        SemanticZoom: _Base.Namespace._lazy(function () {
            var browserStyleEquivalents = _BaseUtils._browserStyleEquivalents;

            var strings = {
                get invalidZoomFactor() { return "Invalid zoomFactor"; },
            };

            function identity(item) {
                return item;
            }

            // Private statics

            var sezoButtonClass = "win-semanticzoom-button";
            var sezoButtonLocationClass = "win-semanticzoom-button-location";
            var sezoButtonShowDuration = 3000;
            var sezoButtonMouseMoveThreshold = 8;

            var semanticZoomClass = "win-semanticzoom";
            var zoomedInElementClass = "win-semanticzoom-zoomedinview";
            var zoomedOutElementClass = "win-semanticzoom-zoomedoutview";

            var zoomChangedEvent = "zoomchanged";

            var bounceFactor = 1.05;
            var defaultZoomFactor = 0.65; // Value used by the shell
            // If we change these we need to update the metadata for the zoomFactor property as well.
            var maxZoomFactor = 0.8;
            var minZoomFactor = 0.2;

            var canvasSizeMax = 4096;

            var outgoingOpacityTransitionDuration = 0.333;
            var incomingOpacityTransitionDuration = 0.333;
            var outgoingScaleTransitionDuration = 0.333;
            var incomingScaleTransitionDuration = 0.333;
            var zoomAnimationDuration = outgoingOpacityTransitionDuration * 1000;
            var zoomAnimationTTFFBuffer = 50;
            // PS 846107 - TransitionEnd event not being fired occassionally if duration is not same
            var bounceInDuration = 0.333;
            var bounceBackDuration = 0.333;
            var easeOutBezier = "cubic-bezier(0.1,0.9,0.2,1)";
            var transformNames = browserStyleEquivalents["transform"];
            var transitionScriptName = browserStyleEquivalents["transition"].scriptName;

            function buildTransition(prop, duration, timing) {
                return prop + " " + _TransitionAnimation._animationTimeAdjustment(duration) + "s " + timing + " " + _TransitionAnimation._libraryDelay + "ms";
            }
            function outgoingElementTransition() {
                return buildTransition(transformNames.cssName, outgoingScaleTransitionDuration, "ease-in-out") + ", " +
                       buildTransition("opacity", outgoingOpacityTransitionDuration, "ease-in-out");
            }

            function incomingElementTransition() {
                return buildTransition(transformNames.cssName, incomingScaleTransitionDuration, "ease-in-out") + ", " +
                       buildTransition("opacity", incomingOpacityTransitionDuration, "ease-in-out");
            }

            function bounceInTransition() {
                return buildTransition(transformNames.cssName, bounceInDuration, easeOutBezier);
            }

            function bounceBackTransition() {
                return buildTransition(transformNames.cssName, bounceBackDuration, easeOutBezier);
            }

            var pinchDistanceCount = 2;
            var zoomOutGestureDistanceChangeFactor = 0.2;
            var zoomInGestureDistanceChangeFactor = 0.45;

            var zoomAnimationTimeout = 1000;

            // The semantic zoom has to piece together information from a variety of separate events to get an understanding of the current
            // manipulation state. Since these events are altogether separate entities, we have to put a delay between the end of one event
            // to allow time for another event to come around. For example, when we handle MSLostPointerCapture events, we need
            // to wait because DManip might be taking over. If it is, we'll receive an MSManipulationStateChanged event soon,
            // and so we don't want to reset our state back, and need give that event a chance to fire.
            var eventTimeoutDelay = 50;

            var PinchDirection = {
                none: 0,
                zoomedIn: 1,
                zoomedOut: 2
            };

            var PT_TOUCH = _ElementUtilities._MSPointerEvent.MSPOINTER_TYPE_TOUCH || "touch";
            var PT_PEN = _ElementUtilities._MSPointerEvent.MSPOINTER_TYPE_PEN || "pen";
            var PT_MOUSE = _ElementUtilities._MSPointerEvent.MSPOINTER_TYPE_MOUSE || "mouse";

            function getDimension(element, property) {
                return _ElementUtilities.convertToPixels(element, property);
            }

            function scaleElement(element, scale) {
                if (_TransitionAnimation.isAnimationEnabled()) {
                    element.style[transformNames.scriptName] = "scale(" + scale + ")";
                }
            }

            var origin = { x: 0, y: 0 };

            function onSemanticZoomResize(ev) {
                var control = ev.target && ev.target.winControl;
                if (control && !control._resizing) {
                    control._onResize();
                }
            }

            function onSemanticZoomPropertyChanged(list) {
                // This will only be called for "aria-checked" changes
                var control = list[0].target && list[0].target.winControl;
                if (control && control instanceof SemanticZoom) {
                    control._onPropertyChanged();
                }
            }

            var SemanticZoom = _Base.Class.define(function SemanticZoom_ctor(element, options) {
                /// <signature helpKeyword="WinJS.UI.SemanticZoom.SemanticZoom">
                /// <summary locid="WinJS.UI.SemanticZoom.constructor">
                /// Creates a new SemanticZoom.
                /// </summary>
                /// <param name="element" domElement="true" locid="WinJS.UI.SemanticZoom.constructor_p:element">
                /// The DOM element that hosts the SemanticZoom.
                /// </param>
                /// <param name="options" type="object" locid="WinJS.UI.SemanticZoom.constructor_p:options">
                /// An object that contains one or more property/value pairs to apply to the new control.
                /// Each property of the options object corresponds to one of the control's properties or events. This parameter is optional.
                /// </param>
                /// <returns type="WinJS.UI.SemanticZoom" locid="WinJS.UI.SemanticZoom.constructor_returnValue">
                /// The new SemanticZoom control.
                /// </returns>
                /// </signature>

                this._disposed = false;

                var that = this;
                var isPhone = _BaseUtils.isPhone;

                this._element = element;
                this._element.winControl = this;
                _ElementUtilities.addClass(this._element, "win-disposable");
                _ElementUtilities.addClass(this._element, semanticZoomClass);
                this._element.setAttribute("role", "ms-semanticzoomcontainer");
                var ariaLabel = this._element.getAttribute("aria-label");
                if (!ariaLabel) {
                    this._element.setAttribute("aria-label", "");
                }

                options = options || {};
                this._zoomedOut = !!options.zoomedOut || !!options.initiallyZoomedOut || false;
                this._enableButton = !isPhone;
                if (!isPhone && options.enableButton !== undefined) {
                    this._enableButton = !!options.enableButton;
                }

                this._element.setAttribute("aria-checked", this._zoomedOut.toString());
                this._zoomFactor = _ElementUtilities._clamp(options.zoomFactor, minZoomFactor, maxZoomFactor, defaultZoomFactor);

                this.zoomedInItem = options.zoomedInItem;
                this.zoomedOutItem = options.zoomedOutItem;

                if (_BaseUtils.validation) {
                    if (options._zoomFactor && options._zoomFactor !== this._zoomFactor) {
                        throw new _ErrorFromName("WinJS.UI.SemanticZoom.InvalidZoomFactor", strings.invalidZoomFactor);
                    }
                }

                this._locked = !!options.locked;

                this._zoomInProgress = false;
                this._isBouncingIn = false;
                this._isBouncing = false;
                this._zooming = false;
                this._aligning = false;
                this._gesturing = false;
                this._gestureEnding = false;
                this._buttonShown = false;
                this._shouldFakeTouchCancel = ("TouchEvent" in _Global);

                // Initialize the control

                this._initialize();
                this._configure();

                // Register event handlers

                this._element.addEventListener("mselementresize", onSemanticZoomResize);
                _ElementUtilities._resizeNotifier.subscribe(this._element, onSemanticZoomResize);
                new _ElementUtilities._MutationObserver(onSemanticZoomPropertyChanged).observe(this._element, { attributes: true, attributeFilter: ["aria-checked"] });

                if (!isPhone) {
                    this._element.addEventListener("wheel", this._onWheel.bind(this), true);
                    this._element.addEventListener("mousewheel", this._onMouseWheel.bind(this), true);
                    this._element.addEventListener("keydown", this._onKeyDown.bind(this), true);

                    _ElementUtilities._addEventListener(this._element, "pointerdown", this._onPointerDown.bind(this), true);
                    _ElementUtilities._addEventListener(this._element, "pointermove", this._onPointerMove.bind(this), true);
                    _ElementUtilities._addEventListener(this._element, "pointerout", this._onPointerOut.bind(this), true);
                    _ElementUtilities._addEventListener(this._element, "pointercancel", this._onPointerCancel.bind(this), true);
                    _ElementUtilities._addEventListener(this._element, "pointerup", this._onPointerUp.bind(this), false);
                    this._hiddenElement.addEventListener("gotpointercapture", this._onGotPointerCapture.bind(this), false);
                    this._hiddenElement.addEventListener("lostpointercapture", this._onLostPointerCapture.bind(this), false);
                    this._element.addEventListener("click", this._onClick.bind(this), true);
                    this._canvasIn.addEventListener(_BaseUtils._browserEventEquivalents["transitionEnd"], this._onCanvasTransitionEnd.bind(this), false);
                    this._canvasOut.addEventListener(_BaseUtils._browserEventEquivalents["transitionEnd"], this._onCanvasTransitionEnd.bind(this), false);
                    this._element.addEventListener("MSContentZoom", this._onMSContentZoom.bind(this), true);
                    this._resetPointerRecords();
                }

                // Get going
                this._onResizeImpl();

                _Control._setOptions(this, options, true);

                // Present the initial view
                that._setVisibility();
            }, {
                // Public members

                /// <field type="HTMLElement" domElement="true" hidden="true" locid="WinJS.UI.SemanticZoom.element" helpKeyword="WinJS.UI.SemanticZoom.element">
                /// The DOM element that hosts the SemanticZoom control.
                /// </field>
                element: {
                    get: function () {
                        return this._element;
                    }
                },

                /// <field type="Boolean" locid="WinJS.UI.SemanticZoom.enableButton" helpKeyword="WinJS.UI.SemanticZoom.enableButton">
                /// Gets or sets a value that specifies whether the semantic zoom button should be displayed or not
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                enableButton: {
                    get: function () {
                        return this._enableButton;
                    },
                    set: function (value) {
                        var newValue = !!value;
                        if (this._enableButton !== newValue && !_BaseUtils.isPhone) {
                            this._enableButton = newValue;
                            if (newValue) {
                                this._createSemanticZoomButton();
                            } else {
                                this._removeSemanticZoomButton();
                            }
                        }
                    }
                },

                /// <field type="Boolean" locid="WinJS.UI.SemanticZoom.zoomedOut" helpKeyword="WinJS.UI.SemanticZoom.zoomedOut">
                /// Gets or sets a value that specifies whether the zoomed out view is currently displayed.
                /// </field>
                zoomedOut: {
                    get: function () {
                        return this._zoomedOut;
                    },
                    set: function (value) {
                        this._zoom(!!value, { x: 0.5 * this._sezoClientWidth, y: 0.5 * this._sezoClientHeight }, false, false, (this._zoomedOut && _BaseUtils.isPhone));
                    }
                },

                /// <field type="Number" locid="WinJS.UI.SemanticZoom.zoomFactor" helpKeyword="WinJS.UI.SemanticZoom.zoomFactor" minimum="0.2" maximum="0.8">
                /// Gets or sets a value between 0.2 and 0.85 that specifies the scale of the zoomed out view. The default is 0.65.
                /// </field>
                zoomFactor: {
                    get: function () {
                        return this._zoomFactor;
                    },
                    set: function (value) {
                        var oldValue = this._zoomFactor;
                        var newValue = _ElementUtilities._clamp(value, minZoomFactor, maxZoomFactor, defaultZoomFactor);
                        if (oldValue !== newValue) {
                            this._zoomFactor = newValue;
                            this._onResize();
                        }
                    }
                },

                /// <field type="Boolean" locid="WinJS.UI.SemanticZoom.locked" helpKeyword="WinJS.UI.SemanticZoom.locked">
                /// Gets or sets a value that indicates whether SemanticZoom is locked and zooming between views is disabled.
                /// </field>
                locked: {
                    get: function () {
                        return this._locked;
                    },
                    set: function (value) {
                        this._locked = !!value;
                        if (value) {
                            this._hideSemanticZoomButton();
                        } else {
                            this._displayButton();
                        }
                    }
                },

                /// <field type="Function" locid="WinJS.UI.SemanticZoom.zoomedInItem" helpKeyword="WinJS.UI.SemanticZoom.zoomedInItem">
                /// Gets or sets a mapping function which can be used to change the item which is targeted on zoom in.
                /// </field>
                zoomedInItem: {
                    get: function () { return this._zoomedInItem; },
                    set: function (value) {
                        this._zoomedInItem = value || identity;
                    },
                },

                /// <field type="Function" locid="WinJS.UI.SemanticZoom.zoomedOutItem" helpKeyword="WinJS.UI.SemanticZoom.zoomedOutItem">
                /// Gets or sets a mapping function which can be used to change the item which is targeted on zoom out.
                /// </field>
                zoomedOutItem: {
                    get: function () { return this._zoomedOutItem; },
                    set: function (value) {
                        this._zoomedOutItem = value || identity;
                    },
                },

                dispose: function () {
                    /// <signature helpKeyword="WinJS.UI.SemanticZoom.dispose">
                    /// <summary locid="WinJS.UI.SemanticZoom.dispose">
                    /// Disposes this SemanticZoom.
                    /// </summary>
                    /// </signature>
                    if (this._disposed) {
                        return;
                    }

                    this._disposed = true;
                    _ElementUtilities._resizeNotifier.unsubscribe(this._element, onSemanticZoomResize);
                    _Dispose._disposeElement(this._elementIn);
                    _Dispose._disposeElement(this._elementOut);

                    this._clearTimeout(this._completeZoomTimer);
                    this._clearTimeout(this._TTFFTimer);
                },

                forceLayout: function () {
                    /// <signature helpKeyword="WinJS.UI.SemanticZoom.forceLayout">
                    /// <summary locid="WinJS.UI.SemanticZoom.forceLayout">
                    /// Forces the SemanticZoom to update its layout. Use this function when making the SemanticZoom visible again
                    /// after its style.display property had been set to "none".
                    /// </summary>
                    /// </signature>
                    this._onResizeImpl();
                },

                // Private members

                _initialize: function () {
                    // initialize the semantic zoom, parent the child controls

                    // Zoomed in and zoomed out controls must be on the first two child elements

                    var children = _ElementListUtilities.children(this._element);
                    this._elementIn = children[0];
                    this._elementOut = children[1];

                    // Ensure the child controls have the same height as the SemanticZoom element

                    this._elementIn.style.height = this._elementOut.style.height = this._element.offsetHeight + "px";

                    // Create the child controls if they haven't been created already

                    ControlProcessor.processAll(this._elementIn);
                    ControlProcessor.processAll(this._elementOut);

                    this._viewIn = this._elementIn.winControl.zoomableView;
                    this._viewOut = this._elementOut.winControl.zoomableView;

                    // Remove the children and place them beneath new divs that will serve as canvases and viewports
                    this._element.removeChild(this._elementOut);
                    this._element.removeChild(this._elementIn);
                    this._element.innerHTML = "";
                    this._cropViewport = _Global.document.createElement("div");
                    this._element.appendChild(this._cropViewport);
                    this._viewportIn = _Global.document.createElement("div");
                    this._opticalViewportIn = _Global.document.createElement("div");
                    this._viewportOut = _Global.document.createElement("div");
                    this._opticalViewportOut = _Global.document.createElement("div");
                    this._opticalViewportIn.appendChild(this._viewportIn);
                    this._opticalViewportOut.appendChild(this._viewportOut);
                    this._cropViewport.appendChild(this._opticalViewportIn);
                    this._cropViewport.appendChild(this._opticalViewportOut);

                    this._canvasIn = _Global.document.createElement("div");
                    this._canvasOut = _Global.document.createElement("div");
                    this._viewportIn.appendChild(this._canvasIn);
                    this._viewportOut.appendChild(this._canvasOut);
                    this._canvasIn.appendChild(this._elementIn);
                    this._canvasOut.appendChild(this._elementOut);

                    if (this._enableButton) {
                        this._createSemanticZoomButton();
                    }

                    this._hiddenElement = _Global.document.createElement("div");
                    this._hiddenElement.tabIndex = -1;
                    this._hiddenElement.visibility = "hidden";
                    this._hiddenElement.setAttribute("aria-hidden", "true");
                    this._element.appendChild(this._hiddenElement);

                    _ElementUtilities.addClass(this._elementIn, zoomedInElementClass);
                    _ElementUtilities.addClass(this._elementOut, zoomedOutElementClass);
                    this._setLayout(this._element, "relative", "hidden");
                    this._setLayout(this._cropViewport, "absolute", "hidden");
                    this._setLayout(this._opticalViewportIn, "absolute", "auto");
                    this._setLayout(this._opticalViewportOut, "absolute", "auto");
                    this._setLayout(this._viewportIn, "absolute", "hidden");
                    this._setLayout(this._viewportOut, "absolute", "hidden");
                    this._setLayout(this._canvasIn, "absolute", "hidden");
                    this._setLayout(this._canvasOut, "absolute", "hidden");
                    // Pinch zoom on a precision touchpad doesn't send PointerMove etc. events like ordinary touch actions. PTP has to be handled specially.
                    // PTP ignores the -ms-touch-action styles that are applied to elements, which means it ignores the style we apply to disable
                    // optical zooming. An element can be optically zoomed via PTP but not with touch. SemanticZoom takes advantage of this fact to
                    // implement zoom for PTPs. The _opticalViewportIn/Out elements have optical zoom properties attached to them to enable
                    // optical zoom, and we attach an MSContentZoom event handler to our root element. When we receive that event on an optical viewport,
                    // and it's in the direction for triggering a zoom, we'll trigger a zoom just like we would for scroll wheel/keyboard.
                    // A nice side effect of this is that we don't need to play the overbounce animation in the PTP code, since optical zoom will
                    // zoom out a small percentage then hit the min/max zoom value for us, then automatically return to 100% once the user stops manipulating.
                    this._setupOpticalViewport(this._opticalViewportIn);
                    this._setupOpticalViewport(this._opticalViewportOut);

                    // Optical zoom can only work on elements with overflow = scroll. The opticalViewportIn/Out elements have overflow=scroll applied to them to enable this,
                    // but we don't want those scrollbars to be visible, so they also have -ms-overflow-style=none.
                    // The -ms-overflow-style on the optical viewport is inherited by its children. We don't want that, so we'll set the immediate children to have the
                    // default overflow style.
                    this._viewportIn.style["-ms-overflow-style"] = "-ms-autohiding-scrollbar";
                    this._viewportOut.style["-ms-overflow-style"] = "-ms-autohiding-scrollbar";

                    this._elementIn.style.position = "absolute";
                    this._elementOut.style.position = "absolute";
                },

                _createSemanticZoomButton: function () {
                    this._sezoButton = _Global.document.createElement("button");
                    this._sezoButton.className = sezoButtonClass + " " + sezoButtonLocationClass;
                    this._sezoButton.tabIndex = -1;
                    this._sezoButton.style.visibility = "hidden";
                    this._sezoButton.setAttribute("aria-hidden", true);
                    this._element.appendChild(this._sezoButton);

                    //register the appropriate events for display the sezo button
                    this._sezoButton.addEventListener("click", this._onSeZoButtonZoomOutClick.bind(this), false);
                    this._element.addEventListener("scroll", this._onSeZoChildrenScroll.bind(this), true);
                    _ElementUtilities._addEventListener(this._element, "pointermove", this._onPenHover.bind(this), false);
                },

                _removeSemanticZoomButton: function () {
                    if (this._sezoButton) {
                        this._element.removeChild(this._sezoButton);
                        this._sezoButton = null;
                    }
                },

                _configure: function () {
                    // Configure the controls for zooming
                    var axisIn = this._viewIn.getPanAxis(),
                        axisOut = this._viewOut.getPanAxis(),
                        isPhone = _BaseUtils.isPhone;
                    this._pansHorizontallyIn = (axisIn === "horizontal" || axisIn === "both");
                    this._pansVerticallyIn = (axisIn === "vertical" || axisIn === "both");
                    this._pansHorizontallyOut = (axisOut === "horizontal" || axisOut === "both");
                    this._pansVerticallyOut = (axisOut === "vertical" || axisOut === "both");

                    if (this._zoomInProgress) {
                        return;
                    }

                    var pagesToPrefetchIn = 1 / this._zoomFactor - 1,
                        pagesToPrefetchOut = bounceFactor - 1;

                    this._setLayout(this._elementIn, "absolute", "visible");
                    this._setLayout(this._elementOut, "absolute", "visible");
                    this._viewIn.configureForZoom(false, !this._zoomedOut, this._zoomFromCurrent.bind(this, true), pagesToPrefetchIn);
                    this._viewOut.configureForZoom(true, this._zoomedOut, this._zoomFromCurrent.bind(this, false), pagesToPrefetchOut);
                    this._pinching = false;
                    this._pinchGesture = 0;
                    this._canvasLeftIn = 0;
                    this._canvasTopIn = 0;
                    this._canvasLeftOut = 0;
                    this._canvasTopOut = 0;

                    // Set scales and opacity when not on the phone
                    if (!isPhone) {
                        if (this._zoomedOut) {
                            scaleElement(this._canvasIn, this._zoomFactor);
                        } else {
                            scaleElement(this._canvasOut, 1 / this._zoomFactor);
                        }
                    }
                    var styleViewportIn = this._opticalViewportIn.style,
                        styleViewportOut = this._opticalViewportOut.style,
                        styleCanvasIn = this._canvasIn.style,
                        styleCanvasOut = this._canvasOut.style;

                    styleCanvasIn.opacity = (this._zoomedOut && !isPhone ? 0 : 1);
                    styleCanvasOut.opacity = (this._zoomedOut ? 1 : 0);
                    // Set the zoomed out canvas to have a higher zIndex than the zoomedIn canvas, so that when hosted on the phone
                    // the SeZo will display both views properly.
                    if (isPhone) {
                        styleCanvasIn.zIndex = 1;
                        styleCanvasOut.zIndex = 2;
                    }

                    // Enable animation
                    if (_TransitionAnimation.isAnimationEnabled() && !isPhone) {
                        styleViewportIn[browserStyleEquivalents["transition-property"].scriptName] = transformNames.cssName;
                        styleViewportIn[browserStyleEquivalents["transition-duration"].scriptName] = "0s";
                        styleViewportIn[browserStyleEquivalents["transition-timing-function"].scriptName] = "linear";

                        styleViewportOut[browserStyleEquivalents["transition-property"].scriptName] = transformNames.cssName;
                        styleViewportOut[browserStyleEquivalents["transition-duration"].scriptName] = "0s";
                        styleViewportOut[browserStyleEquivalents["transition-timing-function"].scriptName] = "linear";
                    }
                },

                _onPropertyChanged: function () {
                    // This will only be called for "aria-checked" changes...also, the list is not important.
                    var newValue = this._element.getAttribute("aria-checked");
                    var zoomedOut = newValue === "true";
                    if (this._zoomedOut !== zoomedOut) {
                        this.zoomedOut = zoomedOut;
                    }
                },

                _onResizeImpl: function () {
                    this._resizing = this._resizing || 0;
                    this._resizing++;
                    try {
                        var positionElement = function (element, left, top, width, height) {
                            var style = element.style;
                            style.left = left + "px";
                            style.top = top + "px";
                            style.width = width + "px";
                            style.height = height + "px";
                        };

                        var sezoComputedStyle = _Global.getComputedStyle(this._element, null),
                            computedWidth = parseFloat(sezoComputedStyle.width),
                            computedHeight = parseFloat(sezoComputedStyle.height),
                            sezoPaddingLeft = getDimension(this._element, sezoComputedStyle["paddingLeft"]),
                            sezoPaddingRight = getDimension(this._element, sezoComputedStyle["paddingRight"]),
                            sezoPaddingTop = getDimension(this._element, sezoComputedStyle["paddingTop"]),
                            sezoPaddingBottom = getDimension(this._element, sezoComputedStyle["paddingBottom"]),
                            viewportWidth = computedWidth - sezoPaddingLeft - sezoPaddingRight,
                            viewportHeight = computedHeight - sezoPaddingTop - sezoPaddingBottom,
                            scaleFactor = 1 / this._zoomFactor;


                        if (this._viewportWidth === viewportWidth && this._viewportHeight === viewportHeight) {
                            return;
                        }
                        this._sezoClientHeight = computedHeight;
                        this._sezoClientWidth = computedWidth;
                        this._viewportWidth = viewportWidth;
                        this._viewportHeight = viewportHeight;

                        this._configure();

                        var multiplierIn = 2 * scaleFactor - 1,
                            canvasInWidth = Math.min(canvasSizeMax, (this._pansHorizontallyIn ? multiplierIn : 1) * viewportWidth),
                            canvasInHeight = Math.min(canvasSizeMax, (this._pansVerticallyIn ? multiplierIn : 1) * viewportHeight);

                        this._canvasLeftIn = 0.5 * (canvasInWidth - viewportWidth);
                        this._canvasTopIn = 0.5 * (canvasInHeight - viewportHeight);
                        positionElement(this._cropViewport, sezoPaddingLeft, sezoPaddingTop, viewportWidth, viewportHeight);
                        positionElement(this._viewportIn, 0, 0, viewportWidth, viewportHeight);
                        positionElement(this._opticalViewportIn, 0, 0, viewportWidth, viewportHeight);
                        positionElement(this._canvasIn, -this._canvasLeftIn, -this._canvasTopIn, canvasInWidth, canvasInHeight);
                        positionElement(this._elementIn, this._canvasLeftIn, this._canvasTopIn, viewportWidth, viewportHeight);

                        var multiplierOut = 2 * bounceFactor - 1,
                            canvasOutWidth = (this._pansHorizontallyOut ? multiplierOut : 1) * viewportWidth,
                            canvasOutHeight = (this._pansVerticallyOut ? multiplierOut : 1) * viewportHeight;

                        this._canvasLeftOut = 0.5 * (canvasOutWidth - viewportWidth);
                        this._canvasTopOut = 0.5 * (canvasOutHeight - viewportHeight);
                        positionElement(this._viewportOut, 0, 0, viewportWidth, viewportHeight);
                        positionElement(this._opticalViewportOut, 0, 0, viewportWidth, viewportHeight);
                        positionElement(this._canvasOut, -this._canvasLeftOut, -this._canvasTopOut, canvasOutWidth, canvasOutHeight);
                        positionElement(this._elementOut, this._canvasLeftOut, this._canvasTopOut, viewportWidth, viewportHeight);
                    } finally {
                        this._resizing--;
                    }
                },

                _onResize: function () {
                    this._onResizeImpl();
                },

                _onMouseMove: function (ev) {
                    if (this._zooming ||
                         (!this._lastMouseX && !this._lastMouseY) ||
                         (ev.screenX === this._lastMouseX && ev.screenY === this._lastMouseY)) {
                        this._lastMouseX = ev.screenX;
                        this._lastMouseY = ev.screenY;
                        return;
                    }

                    if (Math.abs(ev.screenX - this._lastMouseX) <= sezoButtonMouseMoveThreshold &&
                        Math.abs(ev.screenY - this._lastMouseY) <= sezoButtonMouseMoveThreshold) {
                        return;
                    }

                    this._lastMouseX = ev.screenX;
                    this._lastMouseY = ev.screenY;

                    this._displayButton();
                },

                _displayButton: function () {
                    if (!_Hoverable.isHoverable) {
                        return;
                    }

                    _Global.clearTimeout(this._dismissButtonTimer);
                    this._showSemanticZoomButton();

                    var that = this;
                    this._dismissButtonTimer = _Global.setTimeout(function () {
                        that._hideSemanticZoomButton();
                    }, _TransitionAnimation._animationTimeAdjustment(sezoButtonShowDuration));
                },

                _showSemanticZoomButton: function () {
                    if (this._disposed || this._buttonShown) {
                        return;
                    }

                    if (this._sezoButton && !this._zoomedOut && !this._locked) {
                        Animations.fadeIn(this._sezoButton);
                        this._sezoButton.style.visibility = "visible";
                        this._buttonShown = true;
                    }
                },

                _hideSemanticZoomButton: function (immediately) {
                    if (this._disposed || !this._buttonShown) {
                        return;
                    }

                    if (this._sezoButton) {
                        if (!immediately) {
                            var that = this;
                            Animations.fadeOut(this._sezoButton).then(function () {
                                that._sezoButton.style.visibility = "hidden";
                            });
                        } else {
                            this._sezoButton.style.visibility = "hidden";
                        }
                        this._buttonShown = false;
                    }
                },

                _onSeZoChildrenScroll: function (ev) {
                    if (ev.target !== this.element) {
                        this._hideSemanticZoomButton(true);
                    }
                },

                _onWheel: function (ev) {
                    if (ev.ctrlKey) {
                        this._zoom(ev.deltaY > 0, this._getPointerLocation(ev));

                        ev.stopPropagation();
                        ev.preventDefault();
                    }
                },

                _onMouseWheel: function (ev) {
                    if (ev.ctrlKey) {
                        this._zoom(ev.wheelDelta < 0, this._getPointerLocation(ev));

                        ev.stopPropagation();
                        ev.preventDefault();
                    }
                },

                _onPenHover: function (ev) {
                    if (ev.pointerType === PT_PEN && ev.buttons === 0) {
                        this._displayButton();
                    }
                },

                _onSeZoButtonZoomOutClick: function () {
                    this._hideSemanticZoomButton();
                    this._zoom(true, { x: 0.5 * this._sezoClientWidth, y: 0.5 * this._sezoClientHeight }, false);
                },

                _onKeyDown: function (ev) {
                    var handled = false;

                    if (ev.ctrlKey) {
                        var Key = _ElementUtilities.Key;

                        switch (ev.keyCode) {
                            case Key.add:
                            case Key.equal:
                            case 61: //Firefox uses a different keycode
                                this._zoom(false);
                                handled = true;
                                break;

                            case Key.subtract:
                            case Key.dash:
                            case 173: //Firefox uses a different keycode
                                this._zoom(true);
                                handled = true;
                                break;
                        }
                    }

                    if (handled) {
                        ev.stopPropagation();
                        ev.preventDefault();
                    }
                },

                _createPointerRecord: function (ev, fireCancelOnPinch) {
                    var location = this._getPointerLocation(ev);

                    var newRecord = {};
                    newRecord.startX = newRecord.currentX = location.x;
                    newRecord.startY = newRecord.currentY = location.y;
                    newRecord.fireCancelOnPinch = fireCancelOnPinch;

                    this._pointerRecords[ev.pointerId] = newRecord;
                    this._pointerCount = Object.keys(this._pointerRecords).length;

                    return newRecord;
                },

                _deletePointerRecord: function (id) {
                    var record = this._pointerRecords[id];

                    delete this._pointerRecords[id];
                    this._pointerCount = Object.keys(this._pointerRecords).length;

                    if (this._pointerCount !== 2) {
                        this._pinching = false;
                    }

                    return record;
                },

                _fakeCancelOnPointer: function (ev) {
                    var touchEvent = _Global.document.createEvent("UIEvent");
                    touchEvent.initUIEvent("touchcancel", true, true, _Global, 0);
                    touchEvent.touches = ev.touches;
                    touchEvent.targetTouches = ev.targetTouches;
                    touchEvent.changedTouches = [ev._currentTouch];
                    touchEvent._fakedBySemanticZoom = true;
                    ev.target.dispatchEvent(touchEvent);
                },

                _handlePointerDown: function (ev) {
                    this._createPointerRecord(ev, false);

                    // When we get more than one pointer, we need to explicitly set PointerCapture on every pointer we've got to the SemanticZoom.
                    // This will fire lostCapture events on any descendant elements that had called setCapture earlier (for example, ListView items),
                    // and let the hosted control know that the pointer is no longer under its control.
                    var contactKeys = Object.keys(this._pointerRecords);

                    for (var i = 0, len = contactKeys.length; i < len; i++) {
                        try {
                            _ElementUtilities._setPointerCapture(this._hiddenElement, contactKeys[i] || 0);
                        } catch (e) {
                            this._resetPointerRecords();
                            return;
                        }
                    }


                    ev.stopImmediatePropagation();
                    ev.preventDefault();
                },

                _handleFirstPointerDown: function (ev) {
                    this._resetPointerRecords();
                    this._createPointerRecord(ev, this._shouldFakeTouchCancel);
                    this._startedZoomedOut = this._zoomedOut;
                },

                // SeZo wants to prevent clicks while it is playing the bounce animation
                // This can happen when user try to pinch out on the zoomed out view
                // and lift the finger up on the same item
                _onClick: function (ev) {
                    if (ev.target !== this._element) {
                        if (this._isBouncing) {
                            ev.stopImmediatePropagation();
                        }
                    }
                },

                // To optimize perf for ListView and to support more than 2 contact points
                // for custom control, we wire up pointerDown routine for listview during capture
                // but during bubbling phase for everythign else
                _onPointerDown: function (ev) {
                    if (ev.pointerType !== PT_TOUCH) {
                        return;
                    }

                    if (this._pointerCount === 0) {
                        this._handleFirstPointerDown(ev);
                    } else {
                        this._handlePointerDown(ev);
                    }
                },

                // SemanticZoom uses MSPointerMove messages to recognize a pinch. It has to use pointer messages instead of GestureUpdate for a few reasons:
                // 1 - MSGestureUpdate events' scale property (the property that determines pinches) is based on a scalar value. We want our pinch threshold to be pixel based
                // 2 - MSGestureUpdate events' scale property doesn't work when multiple contacts are on multiple surfaces. When that happens .scale will always stay 1.0.
                _onPointerMove: function (ev) {
                    if (ev.pointerType === PT_MOUSE || ev.pointerType === PT_PEN) {
                        this._onMouseMove(ev);
                        return;
                    }

                    if (ev.pointerType !== PT_TOUCH) {
                        return;
                    }

                    function distance(startX, startY, endX, endY) {
                        return Math.sqrt((endX - startX) * (endX - startX) + (endY - startY) * (endY - startY));
                    }

                    function midpoint(point1, point2) {
                        return {
                            x: (0.5 * (point1.currentX + point2.currentX)) | 0,
                            y: (0.5 * (point1.currentY + point2.currentY)) | 0
                        };
                    }

                    var pointerRecord = this._pointerRecords[ev.pointerId],
                        location = this._getPointerLocation(ev);

                    // We listen to MSPointerDown on the bubbling phase of its event, but listen to MSPointerMove on the capture phase.
                    // MSPointerDown can be stopped from bubbling if the underlying control doesn't want the SemanticZoom to interfere for whatever reason.
                    // When that happens, we won't have a pointer record for the event we just got, so there's no sense in doing additional processing.
                    if (!pointerRecord) {
                        return;
                    }
                    pointerRecord.currentX = location.x;
                    pointerRecord.currentY = location.y;

                    if (this._pointerCount === 2) {
                        this._pinching = true;

                        // The order in which these contacts are stored and retrieved from contactKeys is unimportant.  Any two points will suffice."
                        var contactKeys = Object.keys(this._pointerRecords),
                            point1 = this._pointerRecords[contactKeys[0]],
                            point2 = this._pointerRecords[contactKeys[1]];
                        this._currentMidPoint = midpoint(point1, point2);
                        var contactDistance = distance(point1.currentX, point1.currentY, point2.currentX, point2.currentY);
                        var that = this;
                        var processPinchGesture = function (zoomingOut) {
                            var pinchDirection = (zoomingOut ? PinchDirection.zoomedOut : PinchDirection.zoomedIn),
                                gestureReversed = (zoomingOut ? (that._pinchedDirection === PinchDirection.zoomedIn && !that._zoomingOut) : (that._pinchedDirection === PinchDirection.zoomedOut && that._zoomingOut)),
                                canZoomInGesturedDirection = (zoomingOut ? !that._zoomedOut : that._zoomedOut);
                            if (that._pinchedDirection === PinchDirection.none) {
                                if (canZoomInGesturedDirection) {
                                    that._isBouncingIn = false;
                                    that._zoom(zoomingOut, midpoint(point1, point2), true);
                                    that._pinchedDirection = pinchDirection;
                                } else if (!that._isBouncingIn) {
                                    that._playBounce(true, midpoint(point1, point2));
                                }
                            } else if (gestureReversed) {
                                var deltaFromStart = that._lastPinchDistance / that._lastPinchStartDistance;
                                var deltaFromLast = that._lastLastPinchDistance / that._lastPinchDistance;
                                if ((zoomingOut && deltaFromStart > zoomOutGestureDistanceChangeFactor) ||
                                    (!zoomingOut && deltaFromLast > zoomInGestureDistanceChangeFactor)) {
                                    that._zoom(zoomingOut, midpoint(point1, point2), true);
                                    that._pinchedDirection = pinchDirection;
                                }
                            }
                        };
                        this._updatePinchDistanceRecords(contactDistance);
                        if (this._pinchDistanceCount >= pinchDistanceCount) {
                            if (!this._zooming && !this._isBouncing) {
                                _WriteProfilerMark("WinJS.UI.SemanticZoom:EndPinchDetection,info");
                                processPinchGesture(this._lastPinchDirection === PinchDirection.zoomedOut);
                            }
                        }
                    } else if (this._pointerCount > 2) {
                        // When more than two pointers are down, we're not going to interpret that as a pinch, so we reset the distance we'd recorded when it was
                        // just two pointers down.
                        this._resetPinchDistanceRecords();
                    }

                    if (this._pointerCount >= 2) {
                        // When two or more pointers are down, we want to hide all of their move events from the underlying view.
                        // If the pointer we're looking at needs to have a touch cancel event fired for it, we'll fake that now.
                        if (pointerRecord.fireCancelOnPinch) {
                            this._fakeCancelOnPointer(ev, pointerRecord);
                            pointerRecord.fireCancelOnPinch = false;
                        }
                        ev.stopImmediatePropagation();
                        ev.preventDefault();
                    }
                    // If the pointerCount isn't 2, we're no longer making a pinch. This generally happens if you try pinching, find you can't zoom in the pinched direction,
                    // then release one finger. When that happens we need to animate back to normal state.
                    if (this._pointerCount !== 2 && this._isBouncingIn) {
                        this._playBounce(false);
                    }
                },

                _onPointerOut: function (ev) {
                    if (ev.pointerType !== PT_TOUCH || ev.target !== this._element) {
                        return;
                    }

                    this._completePointerUp(ev, false);
                },

                _onPointerUp: function (ev) {
                    this._releasePointerCapture(ev);
                    this._completePointerUp(ev, true);
                    this._completeZoomingIfTimeout();
                },

                _onPointerCancel: function (ev) {
                    if (!ev._fakedBySemanticZoom) {
                        this._releasePointerCapture(ev);
                        this._completePointerUp(ev, false);
                        this._completeZoomingIfTimeout();
                    }
                },

                _onGotPointerCapture: function (ev) {
                    var pointerRecord = this._pointerRecords[ev.pointerId];
                    if (pointerRecord) {
                        pointerRecord.dirty = false;
                    }
                },

                _onLostPointerCapture: function (ev) {
                    var pointerRecord = this._pointerRecords[ev.pointerId];
                    if (pointerRecord) {
                        // If we lose capture on an element, there are three things that could be happening:
                        // 1 - Independent Manipulations are taking over. If that's the case, we should be getting an MSManipulationStateChanged event soon.
                        // 2 - Capture is just moving around inside of the semantic zoom region. We should get a got capture event soon, so we'll want to preserve this record.
                        // 3 - Capture got moved outside of the semantic zoom region. We'll destroy the pointer record if this happens.
                        pointerRecord.dirty = true;
                        var that = this;
                        Promise.timeout(eventTimeoutDelay).then(function () {
                            if (pointerRecord.dirty) {
                                // If the timeout completed and the record is still dirty, we can discard it
                                that._completePointerUp(ev, false);
                            }
                        });
                    }
                },

                _onMSContentZoom: function (ev) {
                    var sourceElement = ev.target;
                    if (sourceElement === this._opticalViewportIn || sourceElement === this._opticalViewportOut) {
                        // msZoomFactor is a floating point, and sometimes it'll won't be exactly 1.0 when at rest. We'll give a 5/1000ths margin above/below 1.0 as the start points for a zoomIn or out gesture.
                        var zoomingOut = (sourceElement.msContentZoomFactor < 0.995),
                            zoomingIn = (sourceElement.msContentZoomFactor > 1.005);
                        if (zoomingOut && !(this._zoomedOut || this._zoomingOut)) {
                            this.zoomedOut = true;
                        } else if (zoomingIn && (this._zoomedOut || this._zoomingOut)) {
                            this.zoomedOut = false;
                        }
                    }
                },

                _updatePinchDistanceRecords: function (contactDistance) {
                    var that = this;
                    function updatePinchDirection(direction) {
                        if (that._lastPinchDirection === direction) {
                            that._pinchDistanceCount++;
                        } else {
                            that._pinchGesture++;
                            that._pinchDistanceCount = 0;
                            that._lastPinchStartDistance = contactDistance;
                        }
                        that._lastPinchDirection = direction;
                        that._lastPinchDistance = contactDistance;
                        that._lastLastPinchDistance = that._lastPinchDistance;
                    }

                    if (this._lastPinchDistance === -1) {
                        _WriteProfilerMark("WinJS.UI.SemanticZoom:StartPinchDetection,info");
                        this._lastPinchDistance = contactDistance;
                    } else {
                        if (this._lastPinchDistance !== contactDistance) {
                            if (this._lastPinchDistance > contactDistance) {
                                updatePinchDirection(PinchDirection.zoomedOut);
                            } else {
                                updatePinchDirection(PinchDirection.zoomedIn);
                            }
                        }
                    }
                },

                _zoomFromCurrent: function (zoomOut) {
                    this._zoom(zoomOut, null, false, true);
                },

                _zoom: function (zoomOut, zoomCenter, gesture, centerOnCurrent, skipAlignment) {
                    _WriteProfilerMark("WinJS.UI.SemanticZoom:StartZoom(zoomOut=" + zoomOut + "),info");

                    this._clearTimeout(this._completeZoomTimer);
                    this._clearTimeout(this._TTFFTimer);

                    this._hideSemanticZoomButton();
                    this._resetPinchDistanceRecords();

                    if (this._locked || this._gestureEnding) {
                        return;
                    }

                    if (this._zoomInProgress) {
                        if (this._gesturing === !gesture) {
                            return;
                        }

                        if (zoomOut !== this._zoomingOut) {
                            // Reverse the zoom that's currently in progress
                            this._startAnimations(zoomOut);
                        }
                    } else if (zoomOut !== this._zoomedOut) {
                        this._zooming = true;
                        this._aligning = true;
                        this._gesturing = !!gesture;

                        if (zoomCenter) {
                            (zoomOut ? this._viewIn : this._viewOut).setCurrentItem(zoomCenter.x, zoomCenter.y);
                        }

                        this._zoomInProgress = true;

                        (zoomOut ? this._opticalViewportOut : this._opticalViewportIn).style.visibility = "visible";
                        if (zoomOut && _BaseUtils.isPhone) {
                            // When on the phone, we need to make sure the zoomed out canvas is visible before calling beginZoom(), otherwise
                            // beginZoom will start up animations on an invisible element, and those animations will be animated dependently.
                            this._canvasOut.style.opacity = 1;
                        }

                        var promiseIn = this._viewIn.beginZoom(),
                            promiseOut = this._viewOut.beginZoom(),
                            beginZoomPromises = null;

                        if ((promiseIn || promiseOut) && _BaseUtils.isPhone) {
                            beginZoomPromises = Promise.join([promiseIn, promiseOut]);
                        }
                        // To simplify zoomableView implementations, only call getCurrentItem between beginZoom and endZoom
                        if (centerOnCurrent && !skipAlignment) {
                            var that = this;
                            (zoomOut ? this._viewIn : this._viewOut).getCurrentItem().then(function (current) {
                                var position = current.position;

                                // Pass in current item to avoid calling getCurrentItem again
                                that._prepareForZoom(zoomOut, {
                                    x: that._rtl() ? (that._sezoClientWidth - position.left - 0.5 * position.width) : position.left + 0.5 * position.width,
                                    y: position.top + 0.5 * position.height
                                }, Promise.wrap(current), beginZoomPromises);
                            });
                        } else {
                            this._prepareForZoom(zoomOut, zoomCenter || {}, null, beginZoomPromises, skipAlignment);
                        }
                    }
                },

                _prepareForZoom: function (zoomOut, zoomCenter, completedCurrentItem, customViewAnimationPromise, skipAlignment) {
                    _WriteProfilerMark("WinJS.UI.SemanticZoom:prepareForZoom,StartTM");
                    var that = this;
                    var centerX = zoomCenter.x,
                        centerY = zoomCenter.y;


                    if (typeof centerX !== "number" || !this._pansHorizontallyIn || !this._pansHorizontallyOut) {
                        centerX = 0.5 * this._sezoClientWidth;
                    }

                    if (typeof centerY !== "number" || !this._pansVerticallyIn || !this._pansVerticallyOut) {
                        centerY = 0.5 * this._sezoClientHeight;
                    }

                    function setZoomCenters(adjustmentIn, adjustmentOut) {
                        that._canvasIn.style[browserStyleEquivalents["transform-origin"].scriptName] = (that._canvasLeftIn + centerX - adjustmentIn.x) + "px " + (that._canvasTopIn + centerY - adjustmentIn.y) + "px";
                        that._canvasOut.style[browserStyleEquivalents["transform-origin"].scriptName] = (that._canvasLeftOut + centerX - adjustmentOut.x) + "px " + (that._canvasTopOut + centerY - adjustmentOut.y) + "px";
                    }

                    setZoomCenters(origin, origin);

                    if (!skipAlignment) {
                        this._alignViewsPromise = this._alignViews(zoomOut, centerX, centerY, completedCurrentItem).then(function () {
                            that._aligning = false;
                            that._gestureEnding = false;
                            that._alignViewsPromise = null;
                            if (!that._zooming && !that._gesturing) {
                                that._completeZoom();
                            }
                        });
                    } else {
                        this._aligning = false;
                    }
                    this._zoomingOut = zoomOut;
                    // Force style resolution
                    _Global.getComputedStyle(this._canvasIn).opacity;
                    _Global.getComputedStyle(this._canvasOut).opacity;
                    _WriteProfilerMark("WinJS.UI.SemanticZoom:prepareForZoom,StopTM");
                    this._startAnimations(zoomOut, customViewAnimationPromise);
                },

                _alignViews: function (zoomOut, centerX, centerY, completedCurrentItem) {
                    var multiplier = (1 - this._zoomFactor),
                        rtl = this._rtl(),
                        offsetLeft = multiplier * (rtl ? this._viewportWidth - centerX : centerX),
                        offsetTop = multiplier * centerY;

                    var that = this;
                    if (zoomOut) {
                        var item = completedCurrentItem || this._viewIn.getCurrentItem();
                        if (item) {
                            return item.then(function (current) {
                                var positionIn = current.position,
                                positionOut = {
                                    left: positionIn.left * that._zoomFactor + offsetLeft,
                                    top: positionIn.top * that._zoomFactor + offsetTop,
                                    width: positionIn.width * that._zoomFactor,
                                    height: positionIn.height * that._zoomFactor
                                };

                                return that._viewOut.positionItem(that._zoomedOutItem(current.item), positionOut);
                            });
                        }
                    } else {
                        var item2 = completedCurrentItem || this._viewOut.getCurrentItem();
                        if (item2) {
                            return item2.then(function (current) {
                                var positionOut = current.position,
                                positionIn = {
                                    left: (positionOut.left - offsetLeft) / that._zoomFactor,
                                    top: (positionOut.top - offsetTop) / that._zoomFactor,
                                    width: positionOut.width / that._zoomFactor,
                                    height: positionOut.height / that._zoomFactor
                                };

                                return that._viewIn.positionItem(that._zoomedInItem(current.item), positionIn);
                            });
                        }
                    }

                    return new Promise(function (c) { c({ x: 0, y: 0 }); });
                },

                _startAnimations: function (zoomOut, customViewAnimationPromise) {
                    this._zoomingOut = zoomOut;

                    var isPhone = _BaseUtils.isPhone;
                    if (_TransitionAnimation.isAnimationEnabled() && !isPhone) {
                        _WriteProfilerMark("WinJS.UI.SemanticZoom:ZoomAnimation,StartTM");
                        this._canvasIn.style[transitionScriptName] = (zoomOut ? outgoingElementTransition() : incomingElementTransition());
                        this._canvasOut.style[transitionScriptName] = (zoomOut ? incomingElementTransition() : outgoingElementTransition());
                    }

                    if (!isPhone) {
                        scaleElement(this._canvasIn, (zoomOut ? this._zoomFactor : 1));
                        scaleElement(this._canvasOut, (zoomOut ? 1 : 1 / this._zoomFactor));
                    }
                    this._canvasIn.style.opacity = (zoomOut && !isPhone ? 0 : 1);
                    if (!isPhone || zoomOut) {
                        this._canvasOut.style.opacity = (zoomOut ? 1 : 0);
                    }

                    if (!_TransitionAnimation.isAnimationEnabled()) {
                        this._zooming = false;
                        this._canvasIn.style[transformNames.scriptName] = "";
                        this._canvasOut.style[transformNames.scriptName] = "";
                        this._completeZoom();
                    } else if (!customViewAnimationPromise) {
                        this.setTimeoutAfterTTFF(this._onZoomAnimationComplete.bind(this), _TransitionAnimation._animationTimeAdjustment(zoomAnimationDuration));
                    } else {
                        var that = this;
                        var onComplete = function onComplete() {
                            that._canvasIn.style[transformNames.scriptName] = "";
                            that._canvasOut.style[transformNames.scriptName] = "";
                            that._onZoomAnimationComplete();
                        };
                        customViewAnimationPromise.then(onComplete, onComplete);
                    }
                },

                _onBounceAnimationComplete: function () {
                    if (!this._isBouncingIn && !this._disposed) {
                        this._completeZoom();
                    }
                },

                _onZoomAnimationComplete: function () {
                    _WriteProfilerMark("WinJS.UI.SemanticZoom:ZoomAnimation,StopTM");

                    if (this._disposed) {
                        return;
                    }
                    this._zooming = false;
                    if (!this._aligning && !this._gesturing && !this._gestureEnding) {
                        this._completeZoom();
                    }
                },

                _onCanvasTransitionEnd: function (ev) {
                    if (this._disposed) {
                        return;
                    }

                    if ((ev.target === this._canvasOut || ev.target === this._canvasIn) && this._isBouncing) {
                        this._onBounceAnimationComplete();
                        return;
                    }

                    if (ev.target === this._canvasIn && ev.propertyName === transformNames.cssName) {
                        this._onZoomAnimationComplete();
                    }
                },

                _clearTimeout: function (timer) {
                    if (timer) {
                        _Global.clearTimeout(timer);
                    }
                },

                _completePointerUp: function (ev, stopPropagation) {
                    if (this._disposed) {
                        return;
                    }

                    var id = ev.pointerId;
                    var pointerRecord = this._pointerRecords[id];
                    if (pointerRecord) {
                        this._deletePointerRecord(id);
                        if (this._isBouncingIn) {
                            this._playBounce(false);
                        }

                        if (stopPropagation && this._pinchedDirection !== PinchDirection.none) {
                            ev.stopImmediatePropagation();
                        }

                        if (this._pointerCount === 0) {
                            // if we are not zooming and if there's any single pending pinch gesture detected that's not being triggered (fast pinch), process them now
                            if (this._pinchGesture === 1 && !this._zooming && this._lastPinchDirection !== PinchDirection.none && this._pinchDistanceCount < pinchDistanceCount) {
                                this._zoom(this._lastPinchDirection === PinchDirection.zoomedOut, this._currentMidPoint, false);
                                this._pinchGesture = 0;
                                this._attemptRecordReset();
                                return;
                            }

                            if (this._pinchedDirection !== PinchDirection.none) {
                                this._gesturing = false;
                                if (!this._aligning && !this._zooming) {
                                    this._completeZoom();
                                }
                            }
                            this._pinchGesture = 0;
                            this._attemptRecordReset();
                        }
                    }
                },

                setTimeoutAfterTTFF: function (callback, delay) {
                    var that = this;
                    that._TTFFTimer = _Global.setTimeout(function () {
                        if (this._disposed) {
                            return;
                        }
                        that._TTFFTimer = _Global.setTimeout(callback, delay);
                    }, zoomAnimationTTFFBuffer);
                },

                _completeZoomingIfTimeout: function () {
                    if (this._pointerCount !== 0) {
                        return;
                    }

                    var that = this;
                    if (this._zoomInProgress || this._isBouncing) {
                        that._completeZoomTimer = _Global.setTimeout(function () {
                            that._completeZoom();
                        }, _TransitionAnimation._animationTimeAdjustment(zoomAnimationTimeout));
                    }
                },

                _completeZoom: function () {
                    if (this._disposed) {
                        return;
                    }

                    if (this._isBouncing) {
                        if (this._zoomedOut) {
                            this._viewOut.endZoom(true);
                        } else {
                            this._viewIn.endZoom(true);
                        }
                        this._isBouncing = false;
                        return;
                    }


                    if (!this._zoomInProgress) {
                        return;
                    }

                    _WriteProfilerMark("WinJS.UI.SemanticZoom:CompleteZoom,info");
                    this._aligning = false;
                    this._alignViewsPromise && this._alignViewsPromise.cancel();

                    this._clearTimeout(this._completeZoomTimer);
                    this._clearTimeout(this._TTFFTimer);

                    this._gestureEnding = false;
                    this[this._zoomingOut ? "_opticalViewportOut" : "_opticalViewportIn"].msContentZoomFactor = 1.0;
                    this._viewIn.endZoom(!this._zoomingOut);
                    this._viewOut.endZoom(this._zoomingOut);
                    this._canvasIn.style.opacity = (this._zoomingOut && !_BaseUtils.isPhone ? 0 : 1);
                    this._canvasOut.style.opacity = (this._zoomingOut ? 1 : 0);

                    this._zoomInProgress = false;

                    var zoomChanged = false;
                    if (this._zoomingOut !== this._zoomedOut) {
                        this._zoomedOut = !!this._zoomingOut;
                        this._element.setAttribute("aria-checked", this._zoomedOut.toString());
                        zoomChanged = true;
                    }

                    this._setVisibility();

                    if (zoomChanged) {
                        // Dispatch the zoomChanged event
                        var ev = _Global.document.createEvent("CustomEvent");
                        ev.initCustomEvent(zoomChangedEvent, true, true, this._zoomedOut);
                        this._element.dispatchEvent(ev);

                        if (this._isActive) {
                            // If the element is no longer a valid focus target, it will throw, we
                            // simply won't do anything in this case
                            _ElementUtilities._setActive(this._zoomedOut ? this._elementOut : this._elementIn);
                        }
                    }

                    _WriteProfilerMark("WinJS.UI.SemanticZoom:CompleteZoom_Custom,info");
                },

                _isActive: function () {
                    var active = _Global.document.activeElement;
                    return this._element === active || this._element.contains(active);
                },

                _setLayout: function (element, position, overflow) {
                    var style = element.style;
                    style.position = position;
                    style.overflow = overflow;
                },

                _setupOpticalViewport: function (viewport) {
                    viewport.style["-ms-overflow-style"] = "none";
                    if (!_BaseUtils.isPhone) {
                        viewport.style["-ms-content-zooming"] = "zoom";
                        // We don't want the optical zoom to be too obvious with PTP (we're mostly just using it to get MSContentZoom events).
                        // We'll use a +/-1% margin around 100% so that we can still optically zoom, but not too far.
                        viewport.style["-ms-content-zoom-limit-min"] = "99%";
                        viewport.style["-ms-content-zoom-limit-max"] = "101%";
                        viewport.style["-ms-content-zoom-snap-points"] = "snapList(100%)";
                        viewport.style["-ms-content-zoom-snap-type"] = "mandatory";
                    }
                },

                _setVisibility: function () {
                    function setVisibility(element, isVisible) {
                        element.style.visibility = (isVisible ? "visible" : "hidden");
                        element.style.zIndex = (isVisible ? "1" : "0");
                    }
                    setVisibility(this._opticalViewportIn, !this._zoomedOut || _BaseUtils.isPhone);
                    setVisibility(this._opticalViewportOut, this._zoomedOut);
                    this._opticalViewportIn.setAttribute("aria-hidden", !!this._zoomedOut);
                    this._opticalViewportOut.setAttribute("aria-hidden", !this._zoomedOut);
                },

                _resetPointerRecords: function () {
                    this._pinchedDirection = PinchDirection.none;
                    this._pointerCount = 0;
                    this._pointerRecords = {};
                    this._resetPinchDistanceRecords();
                },

                _releasePointerCapture: function (ev) {
                    var id = ev.pointerId;
                    try {
                        // Release the pointer capture since they are going away, to allow in air touch pointers
                        // to be reused for multiple interactions
                        _ElementUtilities._releasePointerCapture(this._hiddenElement, id);
                    } catch (e) {
                        // This can throw if the pointer was not already captured
                    }
                },

                _attemptRecordReset: function () {
                    if (this._recordResetPromise) {
                        this._recordResetPromise.cancel();
                    }

                    var that = this;
                    this._recordResetPromise = Promise.timeout(eventTimeoutDelay).then(function () {
                        if (that._pointerCount === 0) {
                            that._resetPointerRecords();
                            that._recordResetPromise = null;
                        }
                    });
                },

                _resetPinchDistanceRecords: function () {
                    this._lastPinchDirection = PinchDirection.none;
                    this._lastPinchDistance = -1;
                    this._lastLastPinchDistance = -1;
                    this._pinchDistanceCount = 0;
                    this._currentMidPoint = null;
                },

                _getPointerLocation: function (ev) {
                    // Get pointer location returns co-ordinate in the sezo control co-ordinate space
                    var sezoBox = { left: 0, top: 0 };
                    try {
                        sezoBox = this._element.getBoundingClientRect();
                    }
                    catch (err) { }  // an exception can be thrown if SeZoDiv is no longer available

                    var sezoComputedStyle = _Global.getComputedStyle(this._element, null),
                        sezoPaddingLeft = getDimension(this._element, sezoComputedStyle["paddingLeft"]),
                        sezoPaddingTop = getDimension(this._element, sezoComputedStyle["paddingTop"]),
                        sezoBorderLeft = getDimension(this._element, sezoComputedStyle["borderLeftWidth"]);

                    return {
                        x: +ev.clientX === ev.clientX ? (ev.clientX - sezoBox.left - sezoPaddingLeft - sezoBorderLeft) : 0,
                        y: +ev.clientY === ev.clientY ? (ev.clientY - sezoBox.top - sezoPaddingTop - sezoPaddingTop) : 0
                    };
                },

                _playBounce: function (beginBounce, center) {
                    if (!_TransitionAnimation.isAnimationEnabled()) {
                        return;
                    }

                    if (this._isBouncingIn === beginBounce) {
                        return;
                    }

                    this._clearTimeout(this._completeZoomTimer);
                    this._clearTimeout(this._TTFFTimer);
                    this._isBouncing = true;
                    this._isBouncingIn = beginBounce;
                    if (beginBounce) {
                        this._bounceCenter = center;
                    } else {
                        this._aligned = true;
                    }

                    var targetElement = (this._zoomedOut ? this._canvasOut : this._canvasIn);
                    var adjustmentX = (this._zoomedOut ? this._canvasLeftOut : this._canvasLeftIn);
                    var adjustmentY = (this._zoomedOut ? this._canvasTopOut : this._canvasTopIn);
                    targetElement.style[browserStyleEquivalents["transform-origin"].scriptName] = (adjustmentX + this._bounceCenter.x) + "px " + (adjustmentY + this._bounceCenter.y) + "px";
                    targetElement.style[transitionScriptName] = beginBounce ? bounceInTransition() : bounceBackTransition();

                    if (!this._zoomedOut) {
                        this._viewIn.beginZoom();
                    } else {
                        this._viewOut.beginZoom();
                    }

                    var scale = (beginBounce ? (this._zoomedOut ? 2 - bounceFactor : bounceFactor) : 1);

                    scaleElement(targetElement, scale);

                    this.setTimeoutAfterTTFF(this._onBounceAnimationComplete.bind(this), _TransitionAnimation._animationTimeAdjustment(zoomAnimationDuration));
                },

                _rtl: function () {
                    return _Global.getComputedStyle(this._element, null).direction === "rtl";
                },

                _pinching: {
                    set: function (value) {
                        this._viewIn.pinching = value;
                        this._viewOut.pinching = value;
                    }
                }
            });
            _Base.Class.mix(SemanticZoom, _Events.createEventProperties("zoomchanged"));
            _Base.Class.mix(SemanticZoom, _Control.DOMEventMixin);
            return SemanticZoom;
        })

    });

});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/Pivot/_Constants',[
    ], function pivotConstantsInit() {
    "use strict";

    var members = {
        // Names of classes used by the Pivot.
        _ClassName: {
            pivot: "win-pivot",
            pivotLocked: "win-pivot-locked",
            pivotTitle: "win-pivot-title",
            pivotHeaders: "win-pivot-headers",
            pivotHeader: "win-pivot-header",
            pivotHeaderSelected: "win-pivot-header-selected",
            pivotViewport: "win-pivot-viewport",
            pivotSurface: "win-pivot-surface",
            pivotNoSnap: "win-pivot-nosnap",
            pivotNavButton: "win-pivot-navbutton",
            pivotNavButtonPrev: "win-pivot-navbutton-prev",
            pivotNavButtonNext: "win-pivot-navbutton-next",
            pivotShowNavButtons: "win-pivot-shownavbuttons",
            pivotInputTypeMouse: "win-pivot-mouse",
            pivotInputTypeTouch: "win-pivot-touch",
        }
    };

    return members;

});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/Pivot/_Item',[
    'exports',
    '../../Core/_Global',
    '../../Core/_Base',
    '../../Core/_BaseUtils',
    '../../Core/_ErrorFromName',
    '../../Core/_Resources',
    '../../ControlProcessor',
    '../../Promise',
    '../../Scheduler',
    '../../Utilities/_Control',
    '../../Utilities/_Dispose',
    '../../Utilities/_ElementUtilities',
    './_Constants'
    ], function pivotItemInit(exports, _Global, _Base, _BaseUtils, _ErrorFromName, _Resources, ControlProcessor, Promise, Scheduler, _Control, _Dispose, _ElementUtilities, _Constants) {
    "use strict";

    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {
        /// <field>
        /// <summary locid="WinJS.UI.PivotItem">
        /// Defines a Item of a Pivot control.
        /// </summary>
        /// <compatibleWith platform="WindowsPhoneApp" minVersion="8.1"/>
        /// </field>
        /// <icon src="ui_winjs.ui.pivotitem.12x12.png" width="12" height="12" />
        /// <icon src="ui_winjs.ui.pivotitem.16x16.png" width="16" height="16" />
        /// <htmlSnippet supportsContent="true"><![CDATA[<div data-win-control="WinJS.UI.PivotItem" data-win-options="{header: 'PivotItem Header'}">PivotItem Content</div>]]></htmlSnippet>
        /// <part name="pivotitem" class="win-pivot-item" locid="WinJS.UI.PivotItem_part:pivotitem">The entire PivotItem control.</part>
        /// <part name="content" class="win-pivot-item-content" locid="WinJS.UI.PivotItem_part:content">The content region of the PivotItem.</part>
        /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
        /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
        /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
        PivotItem: _Base.Namespace._lazy(function () {
            var strings = {
                get duplicateConstruction() { return "Invalid argument: Controls may only be instantiated one time for each DOM element"; }
            };

            var PivotItem = _Base.Class.define(function PivotItem_ctor(element, options) {
                /// <signature helpKeyword="WinJS.UI.PivotItem.PivotItem">
                /// <summary locid="WinJS.UI.PivotItem.constructor">
                /// Creates a new PivotItem.
                /// </summary>
                /// <param name="element" type="HTMLElement" domElement="true" isOptional="true" locid="WinJS.UI.PivotItem.constructor_p:element">
                /// The DOM element that hosts the PivotItem control.
                /// </param>
                /// <param name="options" type="Object" isOptional="true" locid="WinJS.UI.PivotItem.constructor_p:options">
                /// An object that contains one or more property/value pairs to apply to the new control.
                /// Each property of the options object corresponds to one of the control's properties or events.
                /// </param>
                /// <returns type="WinJS.UI.PivotItem" locid="WinJS.UI.PivotItem.constructor_returnValue">
                /// The new PivotItem.
                /// </returns>
                /// <compatibleWith platform="WindowsPhoneApp" minVersion="8.1"/>
                /// </signature>
                element = element || _Global.document.createElement("DIV");
                options = options || {};

                if (element.winControl) {
                    throw new _ErrorFromName("WinJS.UI.PivotItem.DuplicateConstruction", strings.duplicateConstruction);
                }

                // Attaching JS control to DOM element
                element.winControl = this;
                this._element = element;
                _ElementUtilities.addClass(this.element, PivotItem._ClassName.pivotItem);
                _ElementUtilities.addClass(this.element, "win-disposable");
                this._element.setAttribute('role', 'tabpanel');

                this._contentElement = _Global.document.createElement("DIV");
                this._contentElement.className = PivotItem._ClassName.pivotItemContent;
                element.appendChild(this._contentElement);

                // Reparent any existing elements inside the new pivot item content element.
                var elementToMove = this.element.firstChild;
                while (elementToMove !== this._contentElement) {
                    var nextElement = elementToMove.nextSibling;
                    this._contentElement.appendChild(elementToMove);
                    elementToMove = nextElement;
                }

                this._processors = [ControlProcessor.processAll];

                _Control.setOptions(this, options);
            }, {
                /// <field type="HTMLElement" domElement="true" hidden="true" locid="WinJS.UI.PivotItem.element" helpKeyword="WinJS.UI.PivotItem.element">
                /// Gets the DOM element that hosts the PivotItem.
                /// <compatibleWith platform="WindowsPhoneApp" minVersion="8.1"/>
                /// </field>
                element: {
                    get: function () {
                        return this._element;
                    }
                },
                /// <field type="HTMLElement" domElement="true" locid="WinJS.UI.PivotItem.contentElement" helpKeyword="WinJS.UI.PivotItem.contentElement">
                /// Gets the DOM element that hosts the PivotItem's content.
                /// <compatibleWith platform="WindowsPhoneApp" minVersion="8.1"/>
                /// </field>
                contentElement: {
                    get: function () {
                        return this._contentElement;
                    }
                },
                /// <field type="Object" locid="WinJS.UI.PivotItem.header" helpKeyword="WinJS.UI.PivotItem.header">
                /// Get or set the PivotItem's header. After you set this property, the Pivot renders the header again.
                /// <compatibleWith platform="WindowsPhoneApp" minVersion="8.1"/>
                /// </field>
                header: {
                    get: function () {
                        return this._header;
                    },
                    set: function (value) {
                        // Render again even if it is equal to itself.
                        this._header = value;
                        this._parentPivot && this._parentPivot._renderHeaders();
                    }
                },
                _parentPivot: {
                    get: function () {
                        var el = this._element;
                        while (el && !_ElementUtilities.hasClass(el, _Constants._ClassName.pivot)) {
                            el = el.parentNode;
                        }
                        return el && el.winControl;
                    }
                },
                _process: function PivotItem_process() {
                    var that = this;

                    if (this._processors) {
                        this._processors.push(function () {
                            return Scheduler.schedulePromiseAboveNormal();
                        });
                    }

                    this._processed = (this._processors || []).reduce(function (promise, processor) {
                        return promise.then(function () {
                            return processor(that.contentElement);
                        });
                    }, this._processed || Promise.as());
                    this._processors = null;

                    return this._processed;
                },
                dispose: function PivotItem_dispose() {
                    /// <signature helpKeyword="WinJS.UI.PivotItem.dispose">
                    /// <summary locid="WinJS.UI.PivotItem.dispose">
                    /// Disposes this control.
                    /// </summary>
                    /// <compatibleWith platform="WindowsPhoneApp" minVersion="8.1"/>
                    /// </signature>
                    if (this._disposed) {
                        return;
                    }
                    this._disposed = true;
                    this._processors = null;

                    _Dispose.disposeSubTree(this.contentElement);
                }
            }, {
                // Names of classes used by the PivotItem.
                _ClassName: {
                    pivotItem: "win-pivot-item",
                    pivotItemContent: "win-pivot-item-content"
                },
                isDeclarativeControlContainer: _BaseUtils.markSupportedForProcessing(function (item, callback) {
                    if (callback === ControlProcessor.processAll) {
                        return;
                    }

                    item._processors = item._processors || [];
                    item._processors.push(callback);

                    // Once processed the first time synchronously queue up new processors as they come in
                    if (item._processed) {
                        item._process();
                    }
                })
            });

            return PivotItem;
        })
    });

});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/Pivot',[
    '../Core/_Global',
    '../Core/_Base',
    '../Core/_BaseUtils',
    '../Core/_ErrorFromName',
    '../Core/_Events',
    '../Core/_Log',
    '../Core/_Resources',
    '../Core/_WriteProfilerMark',
    '../Animations',
    '../Animations/_TransitionAnimation',
    '../BindingList',
    '../ControlProcessor',
    '../Promise',
    '../Scheduler',
    '../_Signal',
    '../Utilities/_Control',
    '../Utilities/_Dispose',
    '../Utilities/_ElementUtilities',
    '../Utilities/_Hoverable',
    '../Utilities/_TabContainer',
    '../Utilities/_KeyboardBehavior',
    './Pivot/_Constants',
    './Pivot/_Item',
    'require-style!less/desktop/controls',
    'require-style!less/phone/controls'
], function pivotInit(_Global, _Base, _BaseUtils, _ErrorFromName, _Events, _Log, _Resources, _WriteProfilerMark, Animations, _TransitionAnimation, BindingList, ControlProcessor, Promise, Scheduler, _Signal, _Control, _Dispose, _ElementUtilities, _Hoverable, _TabContainer, _KeyboardBehavior, _Constants, _Item) {
    "use strict";

    _Base.Namespace.define("WinJS.UI", {
        /// <field>
        /// <summary locid="WinJS.UI.Pivot">
        /// Tab control which displays a item of content.
        /// </summary>
        /// <compatibleWith platform="WindowsPhoneApp" minVersion="8.1" />
        /// </field>
        /// <icon src="ui_winjs.ui.pivot.12x12.png" width="12" height="12" />
        /// <icon src="ui_winjs.ui.pivot.16x16.png" width="16" height="16" />
        /// <htmlSnippet supportsContent="true"><![CDATA[<div data-win-control="WinJS.UI.Pivot">
        /// <div data-win-control="WinJS.UI.PivotItem" data-win-options="{header: 'PivotItem Header'}">PivotItem Content</div>
        /// </div>]]></htmlSnippet>
        /// <event name="selectionchanged" bubbles="true" locid="WinJS.UI.Pivot_e:selectionchanged">Raised when the item on screen has changed.</event>
        /// <event name="itemanimationstart" bubbles="true" locid="WinJS.UI.Pivot_e:itemloaded">Raised when the item's animation starts.</event>
        /// <event name="itemanimationend" bubbles="true" locid="WinJS.UI.Pivot_e:itemanimationend">Raised when the item's animation ends.</event>
        /// <part name="pivot" class="win-pivot" locid="WinJS.UI.Pivot_part:pivot">The entire Pivot control.</part>
        /// <part name="title" class="win-pivot-title" locid="WinJS.UI.Pivot_part:title">The title for the Pivot control.</part>
        /// <part name="header" class="win-pivot-header" locid="WinJS.UI.Pivot_part:header">A header of a Pivot Item.</part>
        /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
        /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
        /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
        Pivot: _Base.Namespace._lazy(function () {
            var PT_MOUSE = _ElementUtilities._MSPointerEvent.MSPOINTER_TYPE_MOUSE || "mouse";
            var PT_TOUCH = _ElementUtilities._MSPointerEvent.MSPOINTER_TYPE_TOUCH || "touch";
            var Keys = _ElementUtilities.Key;

            function pivotDefaultHeaderTemplate(item) {
                var element = _Global.document.createTextNode(typeof item.header === "object" ? JSON.stringify(item.header) : ('' + item.header));
                return element;
            }

            var createEvent = _Events._createEventProperty;
            var eventNames = {
                selectionChanged: "selectionchanged",
                itemAnimationStart: "itemanimationstart",
                itemAnimationEnd: "itemanimationend",
            };
            var MSManipulationEventStates = _ElementUtilities._MSManipulationEvent;

            var supportsSnap = !!(_ElementUtilities._supportsSnapPoints && _Global.HTMLElement.prototype.msZoomTo);

            var Pivot = _Base.Class.define(function Pivot_ctor(element, options) {
                /// <signature helpKeyword="WinJS.UI.Pivot.Pivot">
                /// <summary locid="WinJS.UI.Pivot.constructor">
                /// Creates a new Pivot control.
                /// </summary>
                /// <param name="element" type="HTMLElement" domElement="true" isOptional="true" locid="WinJS.UI.Pivot.constructor_p:element">
                /// The DOM element that hosts the Pivot control.
                /// </param>
                /// <param name="options" type="Object" isOptional="true" locid="WinJS.UI.Pivot.constructor_p:options">
                /// An object that contains one or more property/value pairs to apply to the new control.
                /// Each property of the options object corresponds to one of the control's properties or events.
                /// Event names must begin with "on". For example, to provide a handler for the index changed event,
                /// add a property named "onselectionchanged" to the options object and set its value to the event handler.
                /// </param>
                /// <returns type="WinJS.UI.Pivot" locid="WinJS.UI.Pivot.constructor_returnValue">
                /// The new Pivot.
                /// </returns>
                /// </signature>
                element = element || _Global.document.createElement("DIV");
                options = options || {};

                if (element.winControl) {
                    throw new _ErrorFromName("WinJS.UI.Pivot.DuplicateConstruction", strings.duplicateConstruction);
                }

                this._handleItemChangedBound = this._handleItemChanged.bind(this);
                this._handleItemInsertedBound = this._handleItemInserted.bind(this);
                this._handleItemMovedBound = this._handleItemMoved.bind(this);
                this._handleItemRemovedBound = this._handleItemRemoved.bind(this);
                this._handleItemReloadBound = this._handleItemReload.bind(this);

                this._id = element.id || _ElementUtilities._uniqueID(element);
                this._writeProfilerMark("constructor,StartTM");

                // Attaching JS control to DOM element
                element.winControl = this;
                this._element = element;
                this._element.setAttribute('role', 'tablist');
                if (!this._element.getAttribute("aria-label")) {
                    this._element.setAttribute('aria-label', strings.pivotAriaLabel);
                }
                if (!supportsSnap) {
                    _ElementUtilities.addClass(this.element, Pivot._ClassName.pivotNoSnap);
                }
                _ElementUtilities.addClass(this.element, Pivot._ClassName.pivot);
                _ElementUtilities.addClass(this.element, "win-disposable");
                _ElementUtilities._addEventListener(this.element, "pointerenter", this._updatePointerType.bind(this));
                _ElementUtilities._addEventListener(this.element, "pointerout", this._updatePointerType.bind(this));
                this._pointerType = PT_MOUSE;

                this._titleElement = _Global.document.createElement("DIV");
                this._titleElement.style.display = "none";
                _ElementUtilities.addClass(this._titleElement, Pivot._ClassName.pivotTitle);
                this._element.appendChild(this._titleElement);

                this._headersContainerElement = _Global.document.createElement("DIV");
                _ElementUtilities.addClass(this._headersContainerElement, Pivot._ClassName.pivotHeaders);
                this._headersContainerElement.addEventListener("keydown", this._headersKeyDown.bind(this));
                this._element.appendChild(this._headersContainerElement);
                this._element.addEventListener('click', this._elementClickedHandler.bind(this));
                _ElementUtilities._addEventListener(this._headersContainerElement, "pointerenter", this._showNavButtons.bind(this));
                _ElementUtilities._addEventListener(this._headersContainerElement, "pointerout", this._hideNavButtons.bind(this));
                _ElementUtilities._addEventListener(this._headersContainerElement, "pointerdown", this._headersPointerDownHandler.bind(this));
                _ElementUtilities._addEventListener(this._headersContainerElement, "pointerup", this._headersPointerUpHandler.bind(this));

                this._winKeyboard = new _KeyboardBehavior._WinKeyboard(this._headersContainerElement);
                this._tabContainer = new _TabContainer.TabContainer(this._headersContainerElement);

                this._viewportElement = _Global.document.createElement("DIV");
                this._viewportElement.className = Pivot._ClassName.pivotViewport;
                this._element.appendChild(this._viewportElement);
                this._viewportElement.setAttribute("role", "group");
                this._viewportElement.setAttribute("aria-label", strings.pivotViewportAriaLabel);
                this._resizeHandlerBound = this._resizeHandler.bind(this);
                this.element.addEventListener("mselementresize", this._resizeHandlerBound);
                _ElementUtilities._resizeNotifier.subscribe(this.element, this._resizeHandlerBound);
                this._viewportWidth = null;
                this._viewportElement.addEventListener("scroll", this._scrollHandler.bind(this));
                this._viewportElement.addEventListener("MSManipulationStateChanged", this._MSManipulationStateChangedHandler.bind(this));
                _ElementUtilities._addEventListener(this._viewportElement, "pointerdown", this._pointerDownHandler.bind(this));

                this._surfaceElement = _Global.document.createElement("DIV");
                this._surfaceElement.className = Pivot._ClassName.pivotSurface;
                this._viewportElement.appendChild(this._surfaceElement);

                this._offsetFromCenter = 0;
                this._currentIndexOnScreen = 0;
                this._loadId = 0;
                this._navMode = Pivot._NavigationModes.none;
                this._currentManipulationState = MSManipulationEventStates.MS_MANIPULATION_STATE_STOPPED;

                // This internally assigns this.items which causes item to be used (even from options) before selectedIndex
                this._parse();

                options = _BaseUtils._shallowCopy(options);
                if (options.items) {
                    // Set this first so selectedIndex and selectedItem can work against the new items.
                    this.items = options.items;
                    delete options.items;
                }

                _Control.setOptions(this, options);

                this._refresh();

                this._writeProfilerMark("constructor,StopTM");
            }, {
                /// <field type="HTMLElement" domElement="true" hidden="true" locid="WinJS.UI.Pivot.element" helpKeyword="WinJS.UI.Pivot.element">
                /// Gets the DOM element that hosts the Pivot.
                /// <compatibleWith platform="WindowsPhoneApp" minVersion="8.1" />
                /// </field>
                element: {
                    get: function () {
                        return this._element;
                    }
                },

                /// <field type="Boolean" locid="WinJS.UI.Pivot.locked" helpKeyword="WinJS.UI.Pivot.locked">
                /// Gets or sets a value that specifies whether the Pivot is locked to the current item.
                /// <compatibleWith platform="WindowsPhoneApp" minVersion="8.1" />
                /// </field>
                locked: {
                    get: function () {
                        return _ElementUtilities.hasClass(this.element, Pivot._ClassName.pivotLocked);
                    },
                    set: function (value) {
                        _ElementUtilities[value ? 'addClass' : 'removeClass'](this.element, Pivot._ClassName.pivotLocked);
                        if (value) {
                            this._hideNavButtons();
                        }
                    }
                },

                /// <field type="WinJS.Binding.List" locid="WinJS.UI.Pivot.items" helpKeyword="WinJS.UI.Pivot.items">
                /// Gets or sets the WinJS.Binding.List of PivotItem objects that belong to this Pivot.
                /// <compatibleWith platform="WindowsPhoneApp" minVersion="8.1" />
                /// </field>
                items: {
                    get: function () {
                        if (this._pendingItems) {
                            return this._pendingItems;
                        }
                        return this._items;
                    },
                    set: function (value) {
                        var resetScrollPosition = !this._pendingItems;
                        this._pendingItems = value;
                        this._refresh();
                        if (resetScrollPosition) {
                            this._pendingIndexOnScreen = 0;
                        }
                    }
                },

                /// <field type="String" locid="WinJS.UI.Pivot.title" helpKeyword="WinJS.UI.Pivot.title">
                /// Gets or sets the title of the Pivot.
                /// <compatibleWith platform="WindowsPhoneApp" minVersion="8.1" />
                /// </field>
                title: {
                    get: function () {
                        return this._titleElement.textContent;
                    },
                    set: function (value) {
                        if (value) {
                            this._titleElement.style.display = "block";
                            this._titleElement.textContent = value;
                        } else {
                            this._titleElement.style.display = "none";
                            this._titleElement.textContent = "";
                        }
                    }
                },

                /// <field type="Number" integer="true" locid="WinJS.UI.Pivot.selectedIndex" helpKeyword="WinJS.UI.Pivot.selectedIndex">
                /// Gets or sets the index of the item in view. This property is useful for restoring a previous view when your app launches or resumes.
                /// <compatibleWith platform="WindowsPhoneApp" minVersion="8.1" />
                /// </field>
                selectedIndex: {
                    get: function () {
                        if (this.items.length === 0) {
                            return -1;
                        }

                        if (+this._pendingIndexOnScreen === this._pendingIndexOnScreen) {
                            return this._pendingIndexOnScreen;
                        }

                        return this._currentIndexOnScreen;
                    },
                    set: function (value) {
                        if (value >= 0 && value < this.items.length) {
                            if (this._pendingRefresh) {
                                this._pendingIndexOnScreen = value;
                            } else {
                                this._navMode = this._navMode || Pivot._NavigationModes.api;
                                this._loadItem(value);
                            }
                        }
                    }
                },

                /// <field type="WinJS.UI.PivotItem" locid="WinJS.UI.Pivot.selectedItem" helpKeyword="WinJS.UI.Pivot.selectedItem">
                /// Gets or sets the item in view. This property is useful for restoring a previous view when your app launches or resumes.
                /// <compatibleWith platform="WindowsPhoneApp" minVersion="8.1" />
                /// </field>
                selectedItem: {
                    get: function () {
                        return this.items.getAt(this.selectedIndex);
                    },
                    set: function (value) {
                        var index = this.items.indexOf(value);
                        if (index !== -1) {
                            this.selectedIndex = index;
                        }
                    }
                },

                dispose: function pivot_dispose() {
                    /// <signature helpKeyword="WinJS.UI.Pivot.dispose">
                    /// <summary locid="WinJS.UI.Pivot.dispose">
                    /// Disposes this control.
                    /// </summary>
                    /// <compatibleWith platform="WindowsPhoneApp" minVersion="8.1" />
                    /// </signature>
                    if (this._disposed) {
                        return;
                    }
                    this._disposed = true;

                    this._updateEvents(this._items);
                    _ElementUtilities._resizeNotifier.unsubscribe(this.element, this._resizeHandlerBound);

                    _Dispose._disposeElement(this._headersContainerElement);

                    for (var i = 0, len = this.items.length; i < len; i++) {
                        this.items.getAt(i).dispose();
                    }
                },

                /// <field type="Function" locid="WinJS.UI.Pivot.onselectionchanged" helpKeyword="WinJS.UI.Pivot.onselectionchanged">
                /// Raised when the user changes to a different item.
                /// <compatibleWith platform="WindowsPhoneApp" minVersion="8.1" />
                /// </field>
                onselectionchanged: createEvent(eventNames.selectionChanged),

                /// <field type="Function" locid="WinJS.UI.Pivot.onitemanimationstart" helpKeyword="WinJS.UI.Pivot.onitemanimationstart">
                /// Raised when the item's animation starts.
                /// <compatibleWith platform="WindowsPhoneApp" minVersion="8.1"/>
                /// </field>
                onitemanimationstart: createEvent(eventNames.itemAnimationStart),

                /// <field type="Function" locid="WinJS.UI.Pivot.onitemanimationend" helpKeyword="WinJS.UI.Pivot.onitemanimationend">
                /// Raised when the item's animation ends.
                /// <compatibleWith platform="WindowsPhoneApp" minVersion="8.1"/>
                /// </field>
                onitemanimationend: createEvent(eventNames.itemAnimationEnd),

                _currentScrollTargetLocation: {
                    get: function () {
                        if (this._viewportWidth === Pivot._invalidViewportWidth) {
                            // ViewportWidth is invalid, this means that the pivot has not been measured yet. This is either because
                            // the pivot is not in the DOM, or there is a pending refresh operation.
                            return 0;
                        }

                        // 49 pages before + current one is 50. There are also 50 afterwards.
                        return (50 + this._offsetFromCenter) * Math.ceil(this._viewportWidth);
                    }
                },

                _pointerType: {
                    get: function () {
                        return this._pointerTypeValue;
                    },
                    set: function (value) {
                        if (this._pointerTypeValue === value) {
                            return;
                        }

                        this._pointerTypeValue = value;
                        if (value === PT_TOUCH) {
                            _ElementUtilities.removeClass(this.element, Pivot._ClassName.pivotInputTypeMouse);
                            _ElementUtilities.addClass(this.element, Pivot._ClassName.pivotInputTypeTouch);
                            this._hideNavButtons();
                        } else {
                            _ElementUtilities.removeClass(this.element, Pivot._ClassName.pivotInputTypeTouch);
                            _ElementUtilities.addClass(this.element, Pivot._ClassName.pivotInputTypeMouse);
                        }
                    }
                },

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

                _viewportWidth: {
                    get: function () {
                        if (!this._viewportElWidth) {
                            this._viewportElWidth = parseFloat(_Global.getComputedStyle(this._viewportElement).width);
                            if (supportsSnap) {
                                this._viewportElement.style[_BaseUtils._browserStyleEquivalents["scroll-snap-points-x"].scriptName] = "snapInterval(0%, " + Math.ceil(this._viewportElWidth) + "px)";
                            }
                        }
                        return this._viewportElWidth || Pivot._invalidViewportWidth;
                    },
                    set: function (value) {
                        this._viewportElWidth = value;
                    }
                },

                // Lifecycle
                _applyProperties: function pivot_applyProperties() {
                    if (this._disposed) {
                        return;
                    }

                    if (this._pendingItems) {
                        this._updateEvents(this._items, this._pendingItems);
                        this._items = this._pendingItems;
                        this._pendingItems = null;
                        // Remove any declaratively specified pivot items before attachItems.
                        while (this.element.firstElementChild !== this._titleElement) {
                            var toRemove = this.element.firstElementChild;
                            toRemove.parentNode.removeChild(toRemove);
                        }
                        _ElementUtilities.empty(this._surfaceElement);
                    }

                    this._attachItems();

                    var pendingIndexOnScreen = this._pendingIndexOnScreen;
                    this._pendingIndexOnScreen = null;
                    this._currentIndexOnScreen = 0;
                    this._skipHeaderSlide = true;

                    this._pendingRefresh = false;
                    this.selectedIndex = Math.min(pendingIndexOnScreen, this.items.length - 1);
                    this._skipHeaderSlide = false;
                    this._recenterUI();
                },

                _attachItems: function pivot_attachItems() {
                    this._measured = false;
                    for (var i = 0, len = this.items.length; i < len; i++) {
                        var item = this._items.getAt(i);

                        if (item.element.parentNode === this._surfaceElement) {
                            throw new _ErrorFromName("WinJS.UI.Pivot.DuplicateItem", strings.duplicateItem);
                        }

                        item.element.style.visibility = "hidden";
                        item.element.style.opacity = 0;

                        this._surfaceElement.appendChild(item.element);
                    }
                },

                _parse: function pivot_parse() {
                    var pivotItems = [];
                    var pivotItemEl = this.element.firstElementChild;

                    while (pivotItemEl !== this._titleElement) {
                        ControlProcessor.processAll(pivotItemEl);

                        var pivotItemContent = pivotItemEl.winControl;
                        if (pivotItemContent) {
                            pivotItems.push(pivotItemContent);
                        } else {
                            throw new _ErrorFromName("WinJS.UI.Pivot.InvalidContent", strings.invalidContent);
                        }

                        var nextItemEl = pivotItemEl.nextElementSibling;
                        pivotItemEl = nextItemEl;
                    }

                    this.items = new BindingList.List(pivotItems);
                },

                _refresh: function pivot_refresh() {
                    if (this._pendingRefresh) {
                        return;
                    }

                    // This is to coalesce property setting operations such as items and scrollPosition.
                    this._pendingRefresh = true;

                    Scheduler.schedule(this._applyProperties.bind(this), Scheduler.Priority.high);
                },

                _renderHeaders: function pivot_renderHeaders(goPrevious) {
                    if (this._pendingRefresh || !this._items) {
                        return;
                    }

                    var restoreFocus = this._headersContainerElement.contains(_Global.document.activeElement);
                    var template = _ElementUtilities._syncRenderer(pivotDefaultHeaderTemplate);

                    _Dispose._disposeElement(this._headersContainerElement);
                    _ElementUtilities.empty(this._headersContainerElement);
                    var cs = _Global.getComputedStyle(this._headersContainerElement);
                    var marginLeft = parseFloat(cs.marginLeft);
                    marginLeft = marginLeft > 0 ? 0 : marginLeft;
                    var marginRight = parseFloat(cs.marginRight);
                    marginRight = marginRight > 0 ? 0 : marginRight;
                    var maxHeaderLength = parseFloat(cs.width) + marginLeft + marginRight;

                    var that = this;
                    function renderHeader(index) {
                        var item = that._items.getAt(index);

                        var headerContainerEl = _Global.document.createElement("BUTTON");
                        headerContainerEl.style.maxWidth = (0.8 * maxHeaderLength) + "px";
                        if (index === that.selectedIndex) {
                            _ElementUtilities.addClass(headerContainerEl, Pivot._ClassName.pivotHeaderSelected);
                            headerContainerEl.setAttribute('aria-selected', true);
                        } else {
                            headerContainerEl.setAttribute('aria-selected', false);
                        }
                        _ElementUtilities.addClass(headerContainerEl, Pivot._ClassName.pivotHeader);
                        headerContainerEl._item = item;
                        template(item, headerContainerEl);
                        headerContainerEl.setAttribute('role', 'tab');
                        that._headersContainerElement.appendChild(headerContainerEl);

                        function ariaSelectedMutated() {
                            if (that._disposed) {
                                return;
                            }

                            if (that._headersContainerElement.contains(headerContainerEl) &&
                                index !== that.selectedIndex &&
                                headerContainerEl.getAttribute('aria-selected') === "true") {
                                // Ignore aria selected changes on selected item.
                                // By selecting another tab we change to it.
                                that.selectedIndex = index;
                            }
                        }

                        new _ElementUtilities._MutationObserver(ariaSelectedMutated).observe(headerContainerEl, { attributes: true, attributeFilter: ["aria-selected"] });
                    }

                    if (this._items.length === 1) {
                        renderHeader(0);
                        this._viewportElement.style.overflow = "hidden";
                        this._headersContainerElement.style.marginLeft = "0px";
                        this._headersContainerElement.style.marginRight = "0px";
                    } else if (this._items.length > 1) {
                        // We always render 1 additional header before the current item.
                        // When going backwards, we render 2 additional headers, the first one as usual, and the second one for
                        // fading out the previous last header.
                        var numberOfHeadersToRender = this._items.length + (goPrevious ? 2 : 1);
                        var indexToRender = this.selectedIndex - 1;

                        if (this._viewportElement.style.overflow) {
                            this._viewportElement.style.overflow = "";
                        }

                        for (var i = 0; i < numberOfHeadersToRender; i++) {
                            if (indexToRender === -1) {
                                indexToRender = this._items.length - 1;
                            } else if (indexToRender === this._items.length) {
                                indexToRender = 0;
                            }

                            renderHeader(indexToRender);
                            indexToRender++;
                        }
                        if (!this._skipHeaderSlide) {
                            var start, end;
                            if (goPrevious) {
                                start = "";
                                end = "0";
                            } else {
                                start = "0";
                                end = "";
                            }

                            var lastHeader = this._headersContainerElement.children[numberOfHeadersToRender - 1];
                            lastHeader.style.opacity = start;
                            var lastHeaderFadeInDuration = 0.167;
                            lastHeader.style[_BaseUtils._browserStyleEquivalents["transition"].scriptName] = "opacity " + _TransitionAnimation._animationTimeAdjustment(lastHeaderFadeInDuration) + "s";
                            _Global.getComputedStyle(lastHeader).opacity;
                            lastHeader.style.opacity = end;
                        }

                        this._headersContainerElement.children[0].setAttribute("aria-hidden", "true");
                        this._headersContainerElement.style.marginLeft = "0px";
                        this._headersContainerElement.style.marginRight = "0px";
                        var leadingMargin = this._rtl ? "marginRight" : "marginLeft";
                        var trailingPadding = this._rtl ? "paddingLeft" : "paddingRight";
                        var firstHeader = this._headersContainerElement.children[0];
                        var leadingSpace = firstHeader.offsetWidth + parseFloat(_Global.getComputedStyle(firstHeader)[leadingMargin]) - parseFloat(_Global.getComputedStyle(firstHeader)[trailingPadding]);
                        if (firstHeader !== this._headersContainerElement.children[0]) {
                            // Calling offsetWidth caused a layout which can trigger a synchronous resize which in turn
                            // calls renderHeaders. We can ignore this one since its the old headers which are not in the DOM.
                            return;
                        }
                        this._headersContainerElement.style[leadingMargin] = (-1 * leadingSpace) + "px";

                        // Create header track nav button elements
                        this._prevButton = _Global.document.createElement("button");
                        _ElementUtilities.addClass(this._prevButton, Pivot._ClassName.pivotNavButton);
                        _ElementUtilities.addClass(this._prevButton, Pivot._ClassName.pivotNavButtonPrev);
                        this._prevButton.addEventListener("click", function () {
                            if (that.locked) {
                                return;
                            }
                            that._rtl ? that._goNext() : that._goPrevious();
                        });
                        this._headersContainerElement.appendChild(this._prevButton);
                        this._prevButton.style.left = this._rtl ? "0px" : leadingSpace + "px";

                        this._nextButton = _Global.document.createElement("button");
                        _ElementUtilities.addClass(this._nextButton, Pivot._ClassName.pivotNavButton);
                        _ElementUtilities.addClass(this._nextButton, Pivot._ClassName.pivotNavButtonNext);
                        this._nextButton.addEventListener("click", function () {
                            if (that.locked) {
                                return;
                            }
                            that._rtl ? that._goPrevious() : that._goNext();
                        });
                        this._headersContainerElement.appendChild(this._nextButton);
                        this._nextButton.style.right = this._rtl ? leadingSpace + "px" : "0px";
                    }
                    var firstHeaderIndex = this._headersContainerElement.children.length > 1 ? 1 : 0;
                    this._tabContainer.childFocus = this._headersContainerElement.children[firstHeaderIndex];
                    if (restoreFocus) {
                        this._headersContainerElement.children[firstHeaderIndex].focus();
                    }
                },

                _resizeHandler: function () {
                    if (this._disposed) {
                        return;
                    }

                    var oldViewportWidth = this._viewportWidth;
                    // Invalidate the viewportWidth
                    this._viewportWidth = null;
                    if (oldViewportWidth !== this._viewportWidth) {
                        _Log.log && _Log.log('_resizeHandler from:' + oldViewportWidth + " to: " + this._viewportWidth);

                        this._hidePivotItemAnimation && this._hidePivotItemAnimation.cancel();
                        this._showPivotItemAnimation && this._showPivotItemAnimation.cancel();
                        this._slideHeadersAnimation && this._slideHeadersAnimation.cancel();

                        this._recenterUI();

                        this._renderHeaders();
                    } else {
                        _Log.log && _Log.log('_resizeHandler worthless resize');
                    }
                },


                // Navigation
                _activateHeader: function pivot_activateHeader(headerElement) {
                    if (this.locked) {
                        return;
                    }

                    var index = this._items.indexOf(headerElement._item);
                    if (index !== this.selectedIndex) {
                        if (!headerElement.previousSibling) {
                            // prevent clicking the previous header
                            return;
                        }
                        this.selectedIndex = index;
                    } else {
                        // Move focus into content for Narrator.
                        _ElementUtilities._setActiveFirstFocusableElement(this.selectedItem.element);
                    }
                },

                _goNext: function pivot_goNext() {
                    if (this.selectedIndex < this._items.length - 1) {
                        this.selectedIndex++;
                    } else {
                        this.selectedIndex = 0;
                    }
                },

                _goPrevious: function pivot_goPrevious() {
                    this._animateToPrevious = true;
                    if (this.selectedIndex > 0) {
                        this.selectedIndex--;
                    } else {
                        this.selectedIndex = this._items.length - 1;
                    }
                    this._animateToPrevious = false;
                },

                _loadItem: function pivot_loadItem(index) {
                    var goPrevious = this._animateToPrevious;
                    this._cachedRTL = _Global.getComputedStyle(this._element, null).direction === "rtl";
                    this._loadId++;
                    var loadId = this._loadId;

                    this._hidePivotItemAnimation && this._hidePivotItemAnimation.cancel();
                    this._showPivotItemAnimation && this._showPivotItemAnimation.cancel();
                    this._slideHeadersAnimation && this._slideHeadersAnimation.cancel();

                    if (this._currentItem) {
                        // Hide existing item
                        this._hidePivotItem(this._currentItem.element, goPrevious);
                    }

                    var oldIndex = this._currentIndexOnScreen;
                    this._currentIndexOnScreen = index;
                    this._slideHeaders(goPrevious, index, oldIndex);

                    if (index < 0) {
                        return;
                    }

                    // Get next item
                    var item = this._items.getAt(index);
                    this._currentItem = item;


                    if (goPrevious) {
                        this._offsetFromCenter--;
                    } else if (index !== oldIndex) {
                        this._offsetFromCenter++;
                    }

                    var zooming = false;
                    if (supportsSnap && this._currentManipulationState !== MSManipulationEventStates.MS_MANIPULATION_STATE_INERTIA) {
                        if (this._skipHeaderSlide) {
                            _Log.log && _Log.log('_skipHeaderSlide index:' + this.selectedIndex + ' offset: ' + this._offsetFromCenter + ' scrollLeft: ' + this._currentScrollTargetLocation, "winjs pivot", "log");
                            _ElementUtilities.setScrollPosition(this._viewportElement, { scrollLeft: this._currentScrollTargetLocation });
                        } else {
                            _Log.log && _Log.log('zoomTo index:' + this.selectedIndex + ' offset: ' + this._offsetFromCenter + ' scrollLeft: ' + this._currentScrollTargetLocation, "winjs pivot", "log");
                            this._viewportElement.msZoomTo({ contentX: this._currentScrollTargetLocation, contentY: 0, viewportX: 0, viewportY: 0 });
                            zooming = this._offsetFromCenter !== 0 && this.items.length > 1;
                        }
                    }

                    var that = this;
                    var selectionChangedDetail = {
                        index: index,
                        direction: goPrevious ? "backwards" : "forward",
                        item: item
                    };
                    this._writeProfilerMark("selectionChanged,info");
                    this._fireEvent(Pivot._EventName.selectionChanged, true, false, selectionChangedDetail);

                    // Start it hidden until it is loaded
                    item._process().then(function () {
                        if (that._disposed || loadId !== that._loadId) {
                            return;
                        }
                        if (supportsSnap) {
                            // Position item:
                            item.element.style[that._getDirectionAccessor()] = that._currentScrollTargetLocation + "px";
                            that._showPivotItem(item.element, goPrevious);
                        } else {
                            // Since we aren't msZoomTo'ing when snap points aren't supported, both the show and hide animations would be
                            // executing on top of each other which produces undesirable visuals. Here we wait for the hide to finish before showing.
                            if (that._hidePivotItemAnimation) {
                                that._showPivotItemAnimation = that._hidePivotItemAnimation.then(function () {
                                    if (that._disposed || loadId !== that._loadId) {
                                        return;
                                    }
                                    return that._showPivotItem(item.element, goPrevious);
                                });
                            } else {
                                // During the very first load or when the hide animation is canceled, we just show the pivot item immediately.
                                that._showPivotItem(item.element, goPrevious);
                            }
                        }
                        var recenterPromise;
                        if (zooming) {
                            if (!that._stoppedAndRecenteredSignal) {
                                that._stoppedAndRecenteredSignal = new _Signal();
                            }
                            recenterPromise = that._stoppedAndRecenteredSignal.promise;
                        } else {
                            recenterPromise = Promise.wrap();
                        }
                        Promise.join([that._slideHeadersAnimation, that._showPivotItemAnimation, that._hidePivotItemAnimation]).then(function () {
                            recenterPromise.then(function () {
                                Promise.timeout(50).then(function () {
                                    if (that._disposed || loadId !== that._loadId) {
                                        return;
                                    }
                                    that._navMode = Pivot._NavigationModes.none;

                                    // Fire event even if animation didn't occur:
                                    that._writeProfilerMark("itemAnimationStop,info");
                                    that._fireEvent(Pivot._EventName.itemAnimationEnd, true);
                                });
                            });
                        });
                    });
                },

                _MSManipulationStateChangedHandler: function pivot_MSManipulationStateChangedHandler(ev) {
                    this._currentManipulationState = ev.currentState;
                    if (!supportsSnap || ev.target !== this._viewportElement) {
                        // Ignore sub scroller manipulations.
                        return;
                    }
                    if (this._currentManipulationState === MSManipulationEventStates.MS_MANIPULATION_STATE_STOPPED) {
                        _Log.log && _Log.log('MSManipulation: Stopped', "winjs pivot", "log");
                    } else if (this._currentManipulationState === MSManipulationEventStates.MS_MANIPULATION_STATE_INERTIA) {
                        _Log.log && _Log.log('MSManipulation: Inertia', "winjs pivot", "log");
                    } else {
                        _Log.log && _Log.log('MSManipulation: Active', "winjs pivot", "log");
                    }

                    if (!this._stoppedAndRecenteredSignal) {
                        this._stoppedAndRecenteredSignal = new _Signal();
                    }

                    this._manipulationRecenterPromise && this._manipulationRecenterPromise.cancel();

                    if (this._currentManipulationState === MSManipulationEventStates.MS_MANIPULATION_STATE_STOPPED) {
                        if (this._navMode === Pivot._NavigationModes.scroll) {
                            this._scrollHandler();
                        }
                        this._navMode = Pivot._NavigationModes.none;

                        var that = this;
                        this._manipulationRecenterPromise = Promise._cancelBlocker(
                            Promise.join([
                                Scheduler.schedulePromiseNormal(null, "WinJS.UI.Pivot._MSManipulationStateChangedHandler_animationPlaceholder"),
                                this._hidePivotItemAnimation,
                                this._showPivotItemAnimation,
                                this._slideHeadersAnimation
                            ])
                        ).then(function () {
                            if (that._disposed) {
                                return;
                            }
                            if (that._currentManipulationState === MSManipulationEventStates.MS_MANIPULATION_STATE_STOPPED) {
                                // If we are still "stopped" we should recenter.
                                _Log.log && _Log.log('Still in Stopped state: calling _recenterUI', "winjs pivot", "log");
                                that._recenterUI();
                            } else {
                                this._stoppedAndRecenteredSignal.complete();
                                this._stoppedAndRecenteredSignal = null;
                            }
                        });
                    } else if (this._currentManipulationState === MSManipulationEventStates.MS_MANIPULATION_STATE_INERTIA) {
                        var destinationX = ev.inertiaDestinationX;
                        if (+destinationX === destinationX) {
                            _Log.log && _Log.log('MSManipulation: inertiaDestinationX: ' + destinationX);
                            var diff = destinationX - this._currentScrollTargetLocation;
                            if (diff > 0) {
                                _Log.log && _Log.log('MSManipulation: Inertia diff > 1', "winjs pivot", "log");
                                this._navMode = Pivot._NavigationModes.inertia;
                                this._goNext();
                            } else if (diff < 0) {
                                _Log.log && _Log.log('MSManipulation: Stopped diff < -1', "winjs pivot", "log");
                                this._navMode = Pivot._NavigationModes.inertia;
                                this._goPrevious();
                            }
                        }
                    }
                },

                _scrollHandler: function pivot_scrollHandler() {
                    if (!supportsSnap || this._disposed) {
                        return;
                    }

                    if (this._recentering && this._stoppedAndRecenteredSignal) {
                        this._stoppedAndRecenteredSignal.complete();
                        this._stoppedAndRecenteredSignal = null;
                        this._recentering = false;
                        return;
                    }

                    if (this._navMode === Pivot._NavigationModes.none || this._navMode === Pivot._NavigationModes.scroll) {
                        this._navMode = Pivot._NavigationModes.scroll;
                        if (this._currentManipulationState === MSManipulationEventStates.MS_MANIPULATION_STATE_STOPPED) {
                            _Log.log && _Log.log('_scrollHandler ScrollPosition: ' + _ElementUtilities.getScrollPosition(this._viewportElement).scrollLeft, "winjs pivot", "log");
                            // Check if narrator user panned/scrolled the Pivot and we are now at an unsupported location.
                            var diff = _ElementUtilities.getScrollPosition(this._viewportElement).scrollLeft - this._currentScrollTargetLocation;
                            this._cachedRTL = _Global.getComputedStyle(this._element, null).direction === "rtl";
                            if (diff > 0) {
                                _Log.log && _Log.log('_scrollHandler diff > 1: ' + diff, "winjs pivot", "log");
                                this._goNext();
                            } else if (diff < 0) {
                                _Log.log && _Log.log('_scrollHandler diff < -1: ' + diff, "winjs pivot", "log");
                                this._goPrevious();
                            }
                        }
                    }
                },

                _recenterUI: function pivot_recenterUI() {
                    if (!supportsSnap) {
                        return;
                    }

                    this._offsetFromCenter = 0;

                    if (_ElementUtilities.getScrollPosition(this._viewportElement).scrollLeft !== this._currentScrollTargetLocation) {
                        // If recentering causes a scroll, then we need to make sure that the next
                        // scroll event event doesn't trigger another navigation
                        this._recentering = true;
                        this._stoppedAndRecenteredSignal = this._stoppedAndRecenteredSignal || new _Signal();
                    } else if (this._stoppedAndRecenteredSignal) {
                        this._stoppedAndRecenteredSignal.complete();
                        this._stoppedAndRecenteredSignal = null;
                    }
                    if (this.selectedItem) {
                        this.selectedItem.element.style[this._getDirectionAccessor()] = this._currentScrollTargetLocation + 'px';
                    }
                    _Log.log && _Log.log('_recenterUI index:' + this.selectedIndex + ' offset: ' + this._offsetFromCenter + ' scrollLeft: ' + this._currentScrollTargetLocation);
                    _ElementUtilities.setScrollPosition(this._viewportElement, { scrollLeft: this._currentScrollTargetLocation });
                },

                _hidePivotItem: function pivot_hidePivotItem(element, goPrevious) {
                    var that = this;
                    function cleanup() {
                        if (that._disposed) {
                            return;
                        }

                        that._hidePivotItemAnimation = null;

                        element.style.visibility = "hidden";
                        element.style.opacity = 0;
                    }

                    var negativeTransform = (this._rtl && !goPrevious) || (goPrevious && !this._rtl);

                    if (_TransitionAnimation.isAnimationEnabled()) {
                        this._hidePivotItemAnimation = Animations[negativeTransform ? "slideRightOut" : "slideLeftOut"](element);
                    } else {
                        this._hidePivotItemAnimation = Scheduler.schedulePromiseNormal(null, "WinJS.UI.Pivot._hidePivotItem_animationPlaceholder");
                    }

                    this._hidePivotItemAnimation.then(cleanup, cleanup);
                },

                _showPivotItem: function pivot_showPivotItem(element, goPrevious) {
                    // Fire the event even if animations are disabled to enable apps to know what is happening
                    this._writeProfilerMark("itemAnimationStart,info");
                    this._fireEvent(Pivot._EventName.itemAnimationStart, true);

                    if (!_TransitionAnimation.isAnimationEnabled()) {
                        element.style.visibility = "";
                        element.style.opacity = "";
                        this._showPivotItemAnimation = null;
                        return;
                    }

                    // Find the elements to slide in
                    var slideGroup1Els = element.querySelectorAll(".win-pivot-slide1");
                    var slideGroup2Els = element.querySelectorAll(".win-pivot-slide2");
                    var slideGroup3Els = element.querySelectorAll(".win-pivot-slide3");

                    var viewportBoundingClientRect = this._viewportElement.getBoundingClientRect();
                    function filterOnScreen(element) {
                        var elementBoundingClientRect = element.getBoundingClientRect();
                        // Can't check left/right since it might be scrolled off.
                        return elementBoundingClientRect.top < viewportBoundingClientRect.bottom &&
                            elementBoundingClientRect.bottom > viewportBoundingClientRect.top;
                    }

                    //Filter the slide groups to the elements actually on screen to avoid animating extra elements
                    slideGroup1Els = Array.prototype.filter.call(slideGroup1Els, filterOnScreen);
                    slideGroup2Els = Array.prototype.filter.call(slideGroup2Els, filterOnScreen);
                    slideGroup3Els = Array.prototype.filter.call(slideGroup3Els, filterOnScreen);

                    var negativeTransform = (this._rtl && !goPrevious) || (goPrevious && !this._rtl);

                    element.style.visibility = "";

                    this._showPivotItemAnimation = Animations[negativeTransform ? "slideRightIn" : "slideLeftIn"](element, slideGroup1Els, slideGroup2Els, slideGroup3Els);

                    var that = this;
                    function showCleanup() {
                        if (that._disposed) {
                            return;
                        }

                        that._showPivotItemAnimation = null;
                    }

                    this._showPivotItemAnimation.then(showCleanup, showCleanup);
                    return this._showPivotItemAnimation;
                },

                _slideHeaders: function pivot_slideHeaders(goPrevious, index, oldIndex) {
                    if (index < 0 || this._skipHeaderSlide) {
                        this._renderHeaders(goPrevious);
                        return;
                    }

                    var targetHeader;

                    if (goPrevious) {
                        targetHeader = this._headersContainerElement.children[0];
                    } else {
                        if (index < oldIndex) {
                            index += this._items.length;
                        }
                        targetHeader = this._headersContainerElement.children[1 + index - oldIndex];
                    }

                    if (!targetHeader) {
                        this._renderHeaders(goPrevious);
                        return;
                    }

                    // Update the selected one:
                    _ElementUtilities.removeClass(this._headersContainerElement.children[1], Pivot._ClassName.pivotHeaderSelected);
                    _ElementUtilities.addClass(targetHeader, Pivot._ClassName.pivotHeaderSelected);

                    var rtl = this._rtl;

                    function offset(element) {
                        if (rtl) {
                            return element.offsetParent.offsetWidth - element.offsetLeft - element.offsetWidth;
                        } else {
                            return element.offsetLeft;
                        }
                    }

                    var endPosition = offset(this._headersContainerElement.children[1]) - offset(targetHeader);
                    if (rtl) {
                        endPosition *= -1;
                    }

                    var that = this;
                    function headerCleanup() {
                        if (that._disposed) {
                            return;
                        }

                        that._renderHeaders(goPrevious);
                        that._slideHeadersAnimation = null;
                    }

                    var headerAnimation;
                    if (_TransitionAnimation.isAnimationEnabled()) {
                        headerAnimation = _TransitionAnimation.executeTransition(
                        this._headersContainerElement.querySelectorAll("." + Pivot._ClassName.pivotHeader),
                        {
                            property: _BaseUtils._browserStyleEquivalents["transform"].cssName,
                            delay: 0,
                            duration: Pivot._headerSlideAnimationDuration,
                            timing: "ease-out",
                            to: "translateX(" + endPosition + "px)"
                        });
                    } else {
                        headerAnimation = Promise.wrap();
                    }

                    this._slideHeadersAnimation = headerAnimation.then(headerCleanup, headerCleanup);
                },


                // Input Handlers
                _elementClickedHandler: function pivot_elementClickedHandler(ev) {
                    if (this.locked || this._navigationHandled) {
                        this._navigationHandled = false;
                        return;
                    }

                    var header;
                    var src = ev.target;
                    if (_ElementUtilities.hasClass(src, Pivot._ClassName.pivotHeader)) {
                        // UIA invoke clicks on the real header elements.
                        header = src;
                    } else {
                        var hitSrcElement = false;
                        var hitTargets = _ElementUtilities._elementsFromPoint(ev.clientX, ev.clientY);
                        if (hitTargets &&
                            // Make sure there aren't any elements obscuring the Pivot headers.
                            // WinJS.Utilities._elementsFromPoint sorts by z order.
                                hitTargets[0] === this._viewportElement) {
                            for (var i = 0, len = hitTargets.length; i < len; i++) {
                                if (hitTargets[i] === src) {
                                    hitSrcElement = true;
                                }
                                if (_ElementUtilities.hasClass(hitTargets[i], Pivot._ClassName.pivotHeader)) {
                                    header = hitTargets[i];
                                }
                            }
                        }

                        if (!hitSrcElement) {
                            // The click's coordinates and source element do not correspond so we
                            // can't trust the coordinates. Ignore the click. This case happens in
                            // clicks triggered by UIA invoke because UIA invoke uses the top left
                            // of the window as the coordinates of every click.
                            header = null;
                        }
                    }

                    if (header) {
                        this._activateHeader(header);
                    }
                },

                _headersKeyDown: function pivot_headersKeydown(e) {
                    if (this.locked) {
                        return;
                    }

                    if (e.keyCode === Keys.leftArrow || e.keyCode === Keys.pageUp) {
                        this._rtl ? this._goNext() : this._goPrevious();
                    } else if (e.keyCode === Keys.rightArrow || e.keyCode === Keys.pageDown) {
                        this._rtl ? this._goPrevious() : this._goNext();
                    }
                },

                _headersPointerDownHandler: function pivot_headersPointerDownHandler(e) {
                    // This prevents Chrome's history navigation swipe gestures.
                    e.preventDefault();

                    this._headersPointerDownPoint = { x: e.clientX, y: e.clientY, type: e.pointerType || "mouse" };
                },

                _headersPointerUpHandler: function pivot_headersPointerUpHandler(e) {
                    if (!this._headersPointerDownPoint || this.locked) {
                        this._headersPointerDownPoint = null;
                        return;
                    }

                    var dx = e.clientX - this._headersPointerDownPoint.x;
                    dx = this._rtl ? -dx : dx;
                    this._navigationHandled = false;
                    if ((!_ElementUtilities._supportsTouchDetection || (this._headersPointerDownPoint.type === e.pointerType && e.pointerType === PT_TOUCH))) {
                        // Header swipe navigation detection
                        // If touch detection is not supported then we will detect swipe gestures for any pointer type.
                        if (dx < -Pivot._headerSwipeTriggerDistance) {
                            this._goNext();
                            this._navigationHandled = true;
                        } else if (dx > Pivot._headerSwipeTriggerDistance) {
                            this._goPrevious();
                            this._navigationHandled = true;
                        }
                    }
                    if (!this._navigationHandled) {
                        // Detect header click
                        var element = e.target;
                        while (element !== null && !_ElementUtilities.hasClass(element, Pivot._ClassName.pivotHeader)) {
                            element = element.parentElement;
                        }
                        if (element !== null) {
                            this._activateHeader(element);
                            this._navigationHandled = true;
                        }
                    }
                    this._headersPointerDownPoint = null;
                },

                _hideNavButtons: function pivot_hideNavButtons(e) {
                    if (e && this._headersContainerElement.contains(e.relatedTarget)) {
                        // Don't hide the nav button if the pointerout event is being fired from going
                        // from one element to another within the header track.
                        return;
                    }
                    _ElementUtilities.removeClass(this._headersContainerElement, Pivot._ClassName.pivotShowNavButtons);
                },

                _pointerDownHandler: function pivot_pointerDownHandler() {
                    _Log.log && _Log.log('_pointerDown', "winjs pivot", "log");
                    // Don't do recentering if a finger is down.
                    this._manipulationRecenterPromise && this._manipulationRecenterPromise.cancel();
                    // If another finger comes down stop animations.
                    this._slideHeadersAnimation && this._slideHeadersAnimation.cancel();
                    this._hidePivotItemAnimation && this._hidePivotItemAnimation.cancel();
                },

                _showNavButtons: function pivot_showNavButtons(e) {
                    if (this.locked || (e && e.pointerType === PT_TOUCH)) {
                        return;
                    }
                    _ElementUtilities.addClass(this._headersContainerElement, Pivot._ClassName.pivotShowNavButtons);
                },


                // Datasource Mutation Handlers
                _handleItemChanged: function pivot_handleItemChanged(ev) {
                    // Change is triggered by binding list setAt() API.
                    if (this._pendingItems) {
                        return;
                    }

                    var index = ev.detail.index;
                    var newItem = ev.detail.newValue;
                    var oldItem = ev.detail.oldValue;
                    if (newItem.element !== oldItem.element) {
                        if (newItem.element.parentNode === this._surfaceElement) {
                            throw new _ErrorFromName("WinJS.UI.Pivot.DuplicateItem", strings.duplicateItem);
                        }

                        newItem.element.style.visibility = "hidden";
                        newItem.element.style.opacity = 0;

                        this._surfaceElement.insertBefore(newItem.element, oldItem.element);
                        this._surfaceElement.removeChild(oldItem.element);

                        if (index === this._currentIndexOnScreen) {
                            this.selectedIndex = this._currentIndexOnScreen;
                        }
                    }

                    this._renderHeaders();
                },

                _handleItemInserted: function pivot_handleItemInserted(ev) {
                    // Insert is triggered by binding list insert APIs such as splice(), push(), and unshift().
                    if (this._pendingItems) {
                        return;
                    }

                    var index = ev.detail.index;
                    var item = ev.detail.value;

                    if (item.element.parentNode === this._surfaceElement) {
                        throw new _ErrorFromName("WinJS.UI.Pivot.DuplicateItem", strings.duplicateItem);
                    }

                    item.element.style.visibility = "hidden";
                    item.element.style.opacity = 0;

                    if (index < this.items.length - 1) {
                        this._surfaceElement.insertBefore(item.element, this.items.getAt(index + 1).element);
                    } else {
                        this._surfaceElement.appendChild(item.element);
                    }

                    if (index <= this._currentIndexOnScreen) {
                        this._currentIndexOnScreen++;
                    }

                    if (this._items.length === 1) {
                        this.selectedIndex = 0;
                    }

                    this._renderHeaders();
                },

                _handleItemMoved: function pivot_handleItemMoved(ev) {
                    // Move is triggered by binding list move() API.
                    if (this._pendingItems) {
                        return;
                    }

                    var oldIndex = ev.detail.oldIndex;
                    var newIndex = ev.detail.newIndex;
                    var item = ev.detail.value;

                    if (newIndex < this.items.length - 1) {
                        this._surfaceElement.insertBefore(item.element, this.items.getAt(newIndex + 1).element);
                    } else {
                        this._surfaceElement.appendChild(item.element);
                    }

                    if (oldIndex < this._currentIndexOnScreen && newIndex >= this._currentIndexOnScreen) {
                        this._currentIndexOnScreen--;
                    } else if (newIndex > this._currentIndexOnScreen && oldIndex <= this._currentIndexOnScreen) {
                        this._currentIndexOnScreen++;
                    } else if (oldIndex === this._currentIndexOnScreen) {
                        this.selectedIndex = newIndex;
                    }

                    this._renderHeaders();
                },

                _handleItemReload: function pivot_handleItemReload() {
                    // Reload is triggered by large operations on the binding list such as reverse(). This causes
                    // _pendingItems to be true which ignores future insert/remove/modified/moved events until the new
                    // items list is applied.
                    this.items = this.items;
                },

                _handleItemRemoved: function pivot_handleItemRemoved(ev) {
                    // Removed is triggered by binding list removal APIs such as splice(), pop(), and shift().
                    if (this._pendingItems) {
                        return;
                    }

                    var item = ev.detail.value;
                    var index = ev.detail.index;

                    this._surfaceElement.removeChild(item.element);

                    if (index < this._currentIndexOnScreen) {
                        this._currentIndexOnScreen--;
                    } else if (index === this._currentIndexOnScreen) {
                        this.selectedIndex = Math.min(this.items.length - 1, this._currentIndexOnScreen);
                    }

                    this._renderHeaders();
                },


                // Misc helpers
                _fireEvent: function pivot_fireEvent(type, canBubble, cancelable, detail) {
                    // Returns true if ev.preventDefault() was not called
                    var event = _Global.document.createEvent("CustomEvent");
                    event.initCustomEvent(type, !!canBubble, !!cancelable, detail);
                    return this.element.dispatchEvent(event);
                },

                _getDirectionAccessor: function () {
                    return this._rtl ? "right" : "left";
                },

                _updateEvents: function pivot_updateEvents(oldItems, newItems) {
                    if (oldItems) {
                        oldItems.removeEventListener("itemchanged", this._handleItemChangedBound);
                        oldItems.removeEventListener("iteminserted", this._handleItemInsertedBound);
                        oldItems.removeEventListener("itemmoved", this._handleItemMovedBound);
                        oldItems.removeEventListener("itemremoved", this._handleItemRemovedBound);
                        oldItems.removeEventListener("reload", this._handleItemReloadBound);
                    }

                    if (newItems) {
                        newItems.addEventListener("itemchanged", this._handleItemChangedBound);
                        newItems.addEventListener("iteminserted", this._handleItemInsertedBound);
                        newItems.addEventListener("itemmoved", this._handleItemMovedBound);
                        newItems.addEventListener("itemremoved", this._handleItemRemovedBound);
                        newItems.addEventListener("reload", this._handleItemReloadBound);
                    }
                },

                _updatePointerType: function pivot_updatePointerType(e) {
                    this._pointerType = e.pointerType || PT_MOUSE;
                },

                _writeProfilerMark: function pivot_writeProfilerMark(text) {
                    var message = "WinJS.UI.Pivot:" + this._id + ":" + text;
                    _WriteProfilerMark(message);
                    _Log.log && _Log.log(message, null, "pivotprofiler");
                },
            }, {
                _invalidViewportWidth: -1,

                _headerSlideAnimationDuration: 250,

                _headerSwipeTriggerDistance: 50,

                // Names of classes used by the Pivot.
                _ClassName: _Constants._ClassName,
                // Names of events fired by the Pivot.
                _EventName: {
                    selectionChanged: eventNames.selectionChanged,
                    itemAnimationStart: eventNames.itemAnimationStart,
                    itemAnimationEnd: eventNames.itemAnimationEnd,
                },
                // These modes keep track of what initiated a scroll/item navigation.
                // The precedence for these modes is inertia > api = scroll, meaning
                // if there is an ongoing pan (inertia), scroll and api calls are
                // ignored. If there is an api call, then scrolling is ignored, and
                // only if there is neither a pan nor api call, scrolling code will
                // execute.
                _NavigationModes: {
                    api: "api",
                    inertia: "inertia",
                    none: "",
                    scroll: "scroll",
                }
            });
            _Base.Class.mix(Pivot, _Control.DOMEventMixin);

            var strings = {
                get duplicateConstruction() { return "Invalid argument: Controls may only be instantiated one time for each DOM element"; },
                get duplicateItem() { return _Resources._getWinJSString("ui/duplicateItem").value; },
                get invalidContent() { return "Invalid content: Pivot content must be made up of PivotItems."; },
                get pivotAriaLabel() { return _Resources._getWinJSString("ui/pivotAriaLabel").value; },
                get pivotViewportAriaLabel() { return _Resources._getWinJSString("ui/pivotViewportAriaLabel").value; }
            };

            return Pivot;
        })
    });
});
// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/Hub/_Section',[
    'exports',
    '../../Core/_Global',
    '../../Core/_Base',
    '../../Core/_BaseUtils',
    '../../Core/_ErrorFromName',
    '../../Core/_Resources',
    '../../ControlProcessor',
    '../../Promise',
    '../../Utilities/_Control',
    '../../Utilities/_Dispose',
    '../../Utilities/_ElementUtilities',
    '../../Utilities/_KeyboardBehavior'
    ], function hubSectionInit(exports, _Global, _Base, _BaseUtils, _ErrorFromName, _Resources, ControlProcessor, Promise, _Control, _Dispose, _ElementUtilities, _KeyboardBehavior) {
    "use strict";

    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {
        /// <field>
        /// <summary locid="WinJS.UI.HubSection">
        /// Defines a section of a Hub control.
        /// </summary>
        /// <compatibleWith platform="Windows" minVersion="8.1"/>
        /// </field>
        /// <icon src="ui_winjs.ui.hubsection.12x12.png" width="12" height="12" />
        /// <icon src="ui_winjs.ui.hubsection.16x16.png" width="16" height="16" />
        /// <htmlSnippet supportsContent="true"><![CDATA[<div data-win-control="WinJS.UI.HubSection" data-win-options="{header: 'HubSection Header'}">HubSection Content</div>]]></htmlSnippet>
        /// <part name="hubsection" class="win-hub-section" locid="WinJS.UI.HubSection_part:hubsection">The entire HubSection control.</part>
        /// <part name="header" class="win-hub-section-header" locid="WinJS.UI.HubSection_part:header">The header region of the HubSection.</part>
        /// <part name="headertabstop" class="win-hub-section-header-tabstop" locid="WinJS.UI.HubSection_part:headertabstop">The tab stop region of the header region of the HubSection.</part>
        /// <part name="headercontent" class="win-hub-section-header-content" locid="WinJS.UI.HubSection_part:headercontent">The content region of the header region of the HubSection.</part>
        /// <part name="headerchevron" class="win-hub-section-header-chevron" locid="WinJS.UI.HubSection_part:headerchevron">The chevron region of the header region of the HubSection.</part>
        /// <part name="content" class="win-hub-section-content" locid="WinJS.UI.HubSection_part:content">The content region of the HubSection.</part>
        /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
        /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
        /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
        HubSection: _Base.Namespace._lazy(function () {
            var strings = {
                get duplicateConstruction() { return "Invalid argument: Controls may only be instantiated one time for each DOM element"; }
            };

            var HubSection = _Base.Class.define(function HubSection_ctor(element, options) {
                /// <signature helpKeyword="WinJS.UI.HubSection.HubSection">
                /// <summary locid="WinJS.UI.HubSection.constructor">
                /// Creates a new HubSection.
                /// </summary>
                /// <param name="element" type="HTMLElement" domElement="true" isOptional="true" locid="WinJS.UI.HubSection.constructor_p:element">
                /// The DOM element that hosts the HubSection control.
                /// </param>
                /// <param name="options" type="Object" isOptional="true" locid="WinJS.UI.HubSection.constructor_p:options">
                /// An object that contains one or more property/value pairs to apply to the new control.
                /// Each property of the options object corresponds to one of the control's properties or events.
                /// </param>
                /// <returns type="WinJS.UI.HubSection" locid="WinJS.UI.HubSection.constructor_returnValue">
                /// The new HubSection.
                /// </returns>
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </signature>
                element = element || _Global.document.createElement("DIV");
                options = options || {};

                if (element.winControl) {
                    throw new _ErrorFromName("WinJS.UI.HubSection.DuplicateConstruction", strings.duplicateConstruction);
                }

                // Attaching JS control to DOM element
                element.winControl = this;
                this._element = element;
                _ElementUtilities.addClass(this.element, HubSection._ClassName.hubSection);
                _ElementUtilities.addClass(this.element, "win-disposable");

                // Not using innerHTML here because there could have been some children already.
                this._headerElement = _Global.document.createElement("DIV");
                this._headerElement.className = HubSection._ClassName.hubSectionHeader;
                this._headerElement.innerHTML =
                    '<button type="button" role="link" class="' + HubSection._ClassName.hubSectionInteractive + ' ' + HubSection._ClassName.hubSectionHeaderTabStop + '">' +
                        '<div class="' +  HubSection._ClassName.hubSectionHeaderWrapper + '" tabindex="-1">' +
                            '<h2 class="' + HubSection._ClassName.hubSectionHeaderContent + ' ' + HubSection._Constants.ellipsisTypeClassName + ' ' + HubSection._Constants.xLargeTypeClassName + '"></h2>' +
                            '<span class="' + HubSection._ClassName.hubSectionHeaderChevron + ' ' + HubSection._Constants.ellipsisTypeClassName + ' ' + HubSection._Constants.xLargeTypeClassName + '"></span>' +
                        '</div>' +
                    '</button>';
                this._headerTabStopElement = this._headerElement.firstElementChild;
                // The purpose of headerWrapperElement is to lay out its children in a flexbox. Ideally, this flexbox would
                // be on headerTabStopElement. However, firefox lays out flexboxes with display:flex differently.
                // Firefox bug 1014285 (Button with display:inline-flex doesn't layout properly)
                // https://bugzilla.mozilla.org/show_bug.cgi?id=1014285
                this._headerWrapperElement = this._headerTabStopElement.firstElementChild;
                this._headerContentElement = this._headerWrapperElement.firstElementChild;
                this._headerChevronElement = this._headerWrapperElement.lastElementChild;
                element.appendChild(this._headerElement);

                this._winKeyboard = new _KeyboardBehavior._WinKeyboard(this._headerElement);

                this._contentElement = _Global.document.createElement("DIV");
                this._contentElement.className = HubSection._ClassName.hubSectionContent;
                this._contentElement.style.visibility = "hidden";
                element.appendChild(this._contentElement);

                // Reparent any existing elements inside the new hub section content element.
                var elementToMove = this.element.firstChild;
                while (elementToMove !== this._headerElement) {
                    var nextElement = elementToMove.nextSibling;
                    this._contentElement.appendChild(elementToMove);
                    elementToMove = nextElement;
                }

                this._processors = [ControlProcessor.processAll];

                _Control.setOptions(this, options);
            }, {
                /// <field type="HTMLElement" domElement="true" hidden="true" locid="WinJS.UI.HubSection.element" helpKeyword="WinJS.UI.HubSection.element">
                /// Gets the DOM element that hosts the HubSection.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                element: {
                    get: function () {
                        return this._element;
                    }
                },
                /// <field type="Boolean" locid="WinJS.UI.HubSection.isHeaderStatic" helpKeyword="WinJS.UI.HubSection.isHeaderStatic">
                /// Gets or sets a value that specifies whether the header is static. Set this value to true to disable clicks and other interactions.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                isHeaderStatic: {
                    get: function () {
                        return this._isHeaderStatic;
                    },
                    set: function (value) {
                        this._isHeaderStatic = value;
                        if (!this._isHeaderStatic) {
                            this._headerTabStopElement.setAttribute("role", "link");
                            _ElementUtilities.addClass(this._headerTabStopElement, HubSection._ClassName.hubSectionInteractive);
                        } else {
                            this._headerTabStopElement.setAttribute("role", "heading");
                            _ElementUtilities.removeClass(this._headerTabStopElement, HubSection._ClassName.hubSectionInteractive);
                        }
                    }
                },
                /// <field type="HTMLElement" domElement="true" locid="WinJS.UI.HubSection.contentElement" helpKeyword="WinJS.UI.HubSection.contentElement">
                /// Gets the DOM element that hosts the HubSection's content.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                contentElement: {
                    get: function () {
                        return this._contentElement;
                    }
                },
                /// <field type="Object" locid="WinJS.UI.HubSection.header" helpKeyword="WinJS.UI.HubSection.header">
                /// Get or set the HubSection's header. After you set this property, the Hub renders the header again.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                header: {
                    get: function () {
                        return this._header;
                    },
                    set: function (value) {
                        // Render again even if it is equal to itself.
                        this._header = value;
                        this._renderHeader();
                    }
                },
                _setHeaderTemplate: function HubSection_setHeaderTemplate(template) {
                    this._template = _ElementUtilities._syncRenderer(template);
                    this._renderHeader();
                },
                _renderHeader: function HubSection_renderHeader() {
                    if (this._template) {
                        _Dispose._disposeElement(this._headerContentElement);
                        _ElementUtilities.empty(this._headerContentElement);
                        this._template(this, this._headerContentElement);
                    }
                },
                _process: function HubSection_process() {
                    var that = this;

                    this._processed = (this._processors || []).reduce(function (promise, processor) {
                        return promise.then(function () {
                            return processor(that.contentElement);
                        });
                    }, this._processed || Promise.as());
                    this._processors = null;

                    return this._processed;
                },
                dispose: function HubSection_dispose() {
                    /// <signature helpKeyword="WinJS.UI.HubSection.dispose">
                    /// <summary locid="WinJS.UI.HubSection.dispose">
                    /// Disposes this control.
                    /// </summary>
                    /// <compatibleWith platform="Windows" minVersion="8.1"/>
                    /// </signature>
                    if (this._disposed) {
                        return;
                    }
                    this._disposed = true;
                    this._processors = null;

                    _Dispose._disposeElement(this._headerContentElement);
                    _Dispose.disposeSubTree(this.contentElement);
                }
            }, {
                // Names of classes used by the HubSection.
                _ClassName: {
                    hubSection: "win-hub-section",
                    hubSectionHeader: "win-hub-section-header",
                    hubSectionHeaderTabStop: "win-hub-section-header-tabstop",
                    hubSectionHeaderWrapper: "win-hub-section-header-wrapper",
                    hubSectionInteractive: "win-hub-section-header-interactive",
                    hubSectionHeaderContent: "win-hub-section-header-content",
                    hubSectionHeaderChevron: "win-hub-section-header-chevron",
                    hubSectionContent: "win-hub-section-content"
                },
                _Constants: {
                    ellipsisTypeClassName: "win-type-ellipsis",
                    xLargeTypeClassName: "win-type-x-large"
                },
                isDeclarativeControlContainer: _BaseUtils.markSupportedForProcessing(function (section, callback) {
                    if (callback === ControlProcessor.processAll) {
                        return;
                    }

                    section._processors = section._processors || [];
                    section._processors.push(callback);

                    // Once processed the first time synchronously queue up new processors as they come in
                    if (section._processed) {
                        section._process();
                    }
                })
            });

            return HubSection;
        })
    });

});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/Hub',[
    '../Core/_Global',
    '../Core/_Base',
    '../Core/_BaseUtils',
    '../Core/_ErrorFromName',
    '../Core/_Events',
    '../Core/_Log',
    '../Core/_Resources',
    '../Core/_WriteProfilerMark',
    '../Animations',
    '../Animations/_TransitionAnimation',
    '../BindingList',
    '../ControlProcessor',
    '../Promise',
    '../_Signal',
    '../Scheduler',
    '../Utilities/_Control',
    '../Utilities/_ElementUtilities',
    '../Utilities/_Hoverable',
    '../Utilities/_UI',
    './Hub/_Section',
    'require-style!less/desktop/controls',
    'require-style!less/phone/controls'
    ], function hubInit(_Global, _Base, _BaseUtils, _ErrorFromName, _Events, _Log, _Resources, _WriteProfilerMark, Animations, _TransitionAnimation, BindingList, ControlProcessor, Promise, _Signal, Scheduler, _Control, _ElementUtilities, _Hoverable, _UI, _Section) {
    "use strict";

    _Base.Namespace.define("WinJS.UI", {
        /// <field>
        /// <summary locid="WinJS.UI.Hub">
        /// Displays sections of content.
        /// </summary>
        /// <compatibleWith platform="Windows" minVersion="8.1"/>
        /// </field>
        /// <icon src="ui_winjs.ui.hub.12x12.png" width="12" height="12" />
        /// <icon src="ui_winjs.ui.hub.16x16.png" width="16" height="16" />
        /// <htmlSnippet supportsContent="true"><![CDATA[<div data-win-control="WinJS.UI.Hub">
        /// <div data-win-control="WinJS.UI.HubSection" data-win-options="{header: 'HubSection Header'}">HubSection Content</div>
        /// </div>]]></htmlSnippet>
        /// <event name="contentanimating" bubbles="true" locid="WinJS.UI.Hub_e:contentanimating">Raised when the Hub is about to play an entrance or a transition animation.</event>
        /// <event name="headerinvoked" bubbles="true" locid="WinJS.UI.Hub_e:headerinvoked">Raised when a header is invoked.</event>
        /// <event name="loadingstatechanged" bubbles="true" locid="WinJS.UI.Hub_e:loadingstatechanged">Raised when the loading state changes.</event>
        /// <part name="hub" class="win-hub" locid="WinJS.UI.Hub_part:hub">The entire Hub control.</part>
        /// <part name="progress" class="win-hub-progress" locid="WinJS.UI.Hub_part:progress">The progress indicator for the Hub.</part>
        /// <part name="viewport" class="win-hub-viewport" locid="WinJS.UI.Hub_part:viewport">The viewport of the Hub.</part>
        /// <part name="surface" class="win-hub-surface" locid="WinJS.UI.Hub_part:surface">The scrollable region of the Hub.</part>
        /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
        /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
        /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
        Hub: _Base.Namespace._lazy(function () {
            var Key = _ElementUtilities.Key;

            function hubDefaultHeaderTemplate(section) {
                var element = _Global.document.createTextNode(typeof section.header === "object" ? JSON.stringify(section.header) : ('' + section.header));
                return element;
            }

            var createEvent = _Events._createEventProperty;
            var eventNames = {
                contentAnimating: "contentanimating",
                headerInvoked: "headerinvoked",
                loadingStateChanged: "loadingstatechanged"
            };

            // Delay time before progress dots are shown when loading hub section(s) on screen.
            var progressDelay = 500;

            var verticalNames = {
                scrollPos: "scrollTop",
                scrollSize: "scrollHeight",
                offsetPos: "offsetTop",
                offsetSize: "offsetHeight",
                oppositeOffsetSize: "offsetWidth",
                marginStart: "marginTop",
                marginEnd: "marginBottom",
                borderStart: "borderTopWidth",
                borderEnd: "borderBottomWidth",
                paddingStart: "paddingTop",
                paddingEnd: "paddingBottom"
            };
            var rtlHorizontalNames = {
                scrollPos: "scrollLeft",
                scrollSize: "scrollWidth",
                offsetPos: "offsetLeft",
                offsetSize: "offsetWidth",
                oppositeOffsetSize: "offsetHeight",
                marginStart: "marginRight",
                marginEnd: "marginLeft",
                borderStart: "borderRightWidth",
                borderEnd: "borderLeftWidth",
                paddingStart: "paddingRight",
                paddingEnd: "paddingLeft"
            };
            var ltrHorizontalNames = {
                scrollPos: "scrollLeft",
                scrollSize: "scrollWidth",
                offsetPos: "offsetLeft",
                offsetSize: "offsetWidth",
                oppositeOffsetSize: "offsetHeight",
                marginStart: "marginLeft",
                marginEnd: "marginRight",
                borderStart: "borderLeftWidth",
                borderEnd: "borderRightWidth",
                paddingStart: "paddingLeft",
                paddingEnd: "paddingRight"
            };

            var Hub = _Base.Class.define(function Hub_ctor(element, options) {
                /// <signature helpKeyword="WinJS.UI.Hub.Hub">
                /// <summary locid="WinJS.UI.Hub.constructor">
                /// Creates a new Hub control.
                /// </summary>
                /// <param name="element" type="HTMLElement" domElement="true" isOptional="true" locid="WinJS.UI.Hub.constructor_p:element">
                /// The DOM element that hosts the Hub control.
                /// </param>
                /// <param name="options" type="Object" isOptional="true" locid="WinJS.UI.Hub.constructor_p:options">
                /// An object that contains one or more property/value pairs to apply to the new control.
                /// Each property of the options object corresponds to one of the control's properties or events.
                /// Event names must begin with "on". For example, to provide a handler for the contentanimating event,
                /// add a property named "oncontentanimating" to the options object and set its value to the event handler.
                /// </param>
                /// <returns type="WinJS.UI.Hub" locid="WinJS.UI.Hub.constructor_returnValue">
                /// The new Hub.
                /// </returns>
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </signature>
                element = element || _Global.document.createElement("DIV");
                options = options || {};

                if (element.winControl) {
                    throw new _ErrorFromName("WinJS.UI.Hub.DuplicateConstruction", strings.duplicateConstruction);
                }

                this._id = element.id || _ElementUtilities._uniqueID(element);
                this._writeProfilerMark("constructor,StartTM");

                this._windowKeyDownHandlerBound = this._windowKeyDownHandler.bind(this);
                _Global.addEventListener('keydown', this._windowKeyDownHandlerBound);

                // Attaching JS control to DOM element
                element.winControl = this;
                this._element = element;
                _ElementUtilities.addClass(this.element, Hub._ClassName.hub);
                _ElementUtilities.addClass(this.element, "win-disposable");

                this._viewportElement = _Global.document.createElement("DIV");
                this._viewportElement.className = Hub._ClassName.hubViewport;
                this._element.appendChild(this._viewportElement);
                this._viewportElement.setAttribute("role", "group");
                this._viewportElement.setAttribute("aria-label", strings.hubViewportAriaLabel);

                this._surfaceElement = _Global.document.createElement("DIV");
                this._surfaceElement.className = Hub._ClassName.hubSurface;
                this._viewportElement.appendChild(this._surfaceElement);

                // Start invisible so that you do not see the content loading until the sections are ready.
                this._visible = false;
                this._viewportElement.style.opacity = 0;

                if (!options.orientation) {
                    this._orientation = _UI.Orientation.horizontal;
                    _ElementUtilities.addClass(this.element, Hub._ClassName.hubHorizontal);
                }

                this._fireEntrance = true;
                this._animateEntrance = true;
                this._loadId = 0;
                this.runningAnimations = new Promise.wrap();
                this._currentIndexForSezo = 0;

                // This internally assigns this.sections which causes section to be used (even from options) before
                // scrollPosition or sectionOnScreen.
                this._parse();

                _Control.setOptions(this, options);

                _ElementUtilities._addEventListener(this.element, "focusin", this._focusin.bind(this), false);
                this.element.addEventListener("keydown", this._keyDownHandler.bind(this));
                this.element.addEventListener("click", this._clickHandler.bind(this));
                this._resizeHandlerBound = this._resizeHandler.bind(this);
                this.element.addEventListener("mselementresize", this._resizeHandlerBound);
                _ElementUtilities._resizeNotifier.subscribe(this.element, this._resizeHandlerBound);
                this._viewportElement.addEventListener("scroll", this._scrollHandler.bind(this));
                this._surfaceElement.addEventListener("mselementresize", this._contentResizeHandler.bind(this));

                this._handleSectionChangedBind = this._handleSectionChanged.bind(this);
                this._handleSectionInsertedBind = this._handleSectionInserted.bind(this);
                this._handleSectionMovedBind = this._handleSectionMoved.bind(this);
                this._handleSectionRemovedBind = this._handleSectionRemoved.bind(this);
                this._handleSectionReloadBind = this._handleSectionReload.bind(this);

                this._refresh();

                this._writeProfilerMark("constructor,StopTM");
            }, {
                /// <field type="HTMLElement" domElement="true" hidden="true" locid="WinJS.UI.Hub.element" helpKeyword="WinJS.UI.Hub.element">
                /// Gets the DOM element that hosts the Hub.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                element: {
                    get: function () {
                        return this._element;
                    }
                },
                /// <field type="String" oamOptionsDatatype="WinJS.UI.Orientation" locid="WinJS.UI.Hub.orientation" helpKeyword="WinJS.UI.Hub.orientation">
                /// Gets or sets the orientation of sections within the Hub.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                orientation: {
                    get: function () {
                        return this._orientation;
                    },
                    set: function (value) {
                        if (value === this._orientation) {
                            return;
                        }
                        this._measured = false;
                        // clear existing scroll before we switch orientation
                        if (this._names) { // handle setting orientation before we measure
                            var newScrollPos = {};
                            newScrollPos[this._names.scrollPos] = 0;
                            _ElementUtilities.setScrollPosition(this._viewportElement, newScrollPos);
                        }
                        if (value === _UI.Orientation.vertical) {
                            _ElementUtilities.removeClass(this.element, Hub._ClassName.hubHorizontal);
                            _ElementUtilities.addClass(this.element, Hub._ClassName.hubVertical);
                        } else {
                            value = _UI.Orientation.horizontal;
                            _ElementUtilities.removeClass(this.element, Hub._ClassName.hubVertical);
                            _ElementUtilities.addClass(this.element, Hub._ClassName.hubHorizontal);
                        }
                        this._orientation = value;
                        Scheduler.schedule(this._updateSnapList.bind(this), Scheduler.Priority.idle);
                    }
                },
                /// <field type="WinJS.Binding.List" locid="WinJS.UI.Hub.sections" helpKeyword="WinJS.UI.Hub.sections">
                /// Gets or sets the WinJS.Binding.List of HubSection objects that belong to this Hub.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                sections: {
                    get: function () {
                        if (this._pendingSections) {
                            return this._pendingSections;
                        }
                        return this._sections;
                    },
                    set: function (value) {
                        var resetScrollPosition = !this._pendingSections;
                        this._pendingSections = value;
                        this._refresh();
                        if (resetScrollPosition) {
                            this.scrollPosition = 0;
                        }
                    }
                },
                /// <field type="Object" locid="WinJS.UI.Hub.headerTemplate" helpKeyword="WinJS.UI.Hub.headerTemplate" potentialValueSelector="[data-win-control='WinJS.Binding.Template']">
                /// Gets or sets the WinJS.Binding.Template or template function that creates the DOM elements
                /// which represent the header for each HubSection. Each header can
                /// contain multiple DOM elements, but we recommend that it have a single
                /// root element.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                headerTemplate: {
                    get: function () {
                        if (this._pendingHeaderTemplate) {
                            return this._pendingHeaderTemplate;
                        }

                        if (!this._headerTemplate) {
                            this._headerTemplate = hubDefaultHeaderTemplate;
                        }

                        return this._headerTemplate;
                    },
                    set: function (value) {
                        this._pendingHeaderTemplate = value || hubDefaultHeaderTemplate;
                        this._refresh();
                    }
                },
                /// <field type="Number" integer="true" locid="WinJS.UI.Hub.scrollPosition" helpKeyword="WinJS.UI.Hub.scrollPosition">
                /// Gets or sets the position of the Hub's scrollbar.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                scrollPosition: {
                    get: function () {
                        if (+this._pendingScrollLocation === this._pendingScrollLocation) {
                            return this._pendingScrollLocation;
                        }

                        this._measure();
                        return this._scrollPosition;
                    },
                    set: function (value) {
                        value = Math.max(0, value);
                        if (this._pendingRefresh) {
                            // Unable to constrain length because sections may have changed.
                            this._pendingScrollLocation = value;
                            this._pendingSectionOnScreen = null;
                        } else {
                            this._measure();
                            var targetScrollPos = Math.max(0, Math.min(this._scrollLength - this._viewportSize, value));
                            this._scrollPosition = targetScrollPos;
                            var newScrollPos = {};
                            newScrollPos[this._names.scrollPos] = targetScrollPos;
                            _ElementUtilities.setScrollPosition(this._viewportElement, newScrollPos);
                        }
                    }
                },

                /// <field type="Number" integer="true" locid="WinJS.UI.Hub.sectionOnScreen" helpKeyword="WinJS.UI.Hub.sectionOnScreen">
                /// Gets or sets the index of first section in view. This property is useful for restoring a previous view when your app launches or resumes.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                sectionOnScreen: {
                    get: function () {
                        if (+this._pendingSectionOnScreen === this._pendingSectionOnScreen) {
                            return this._pendingSectionOnScreen;
                        }

                        this._measure();
                        for (var i = 0; i < this._sectionSizes.length; i++) {
                            var sectionSize = this._sectionSizes[i];
                            if ((sectionSize.offset + sectionSize.size - sectionSize.borderEnd - sectionSize.paddingEnd) > (this._scrollPosition + this._startSpacer + sectionSize.borderStart + sectionSize.paddingStart)) {
                                return i;
                            }
                        }
                        return -1;
                    },
                    set: function (value) {
                        value = Math.max(0, value);
                        if (this._pendingRefresh) {
                            this._pendingSectionOnScreen = value;
                            this._pendingScrollLocation = null;
                        } else {
                            this._measure();
                            if (value >= 0 && value < this._sectionSizes.length) {
                                this._scrollToSection(value);
                            }
                        }
                    }
                },
                /// <field type="Number" integer="true" isAdvanced="true" locid="WinJS.UI.Hub.indexOfFirstVisible" helpKeyword="WinJS.UI.Hub.indexOfFirstVisible">
                /// Gets or sets the index of first section at least partially in view. Use for animations.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                indexOfFirstVisible: {
                    get: function () {
                        this._measure();
                        for (var i = 0; i < this._sectionSizes.length; i++) {
                            var sectionSize = this._sectionSizes[i];
                            if ((sectionSize.offset + sectionSize.size - sectionSize.borderEnd - sectionSize.paddingEnd) > this._scrollPosition) {
                                return i;
                            }
                        }
                        return -1;
                    }
                },
                /// <field type="Number" integer="true" isAdvanced="true" locid="WinJS.UI.Hub.indexOfLastVisible" helpKeyword="WinJS.UI.Hub.indexOfLastVisible">
                /// Gets or sets the index of last section at least partially in view. Use for animations.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                indexOfLastVisible: {
                    get: function () {
                        this._measure();
                        for (var i = this._sectionSizes.length - 1; i >= 0; i--) {
                            var sectionSize = this._sectionSizes[i];
                            if ((sectionSize.offset + sectionSize.paddingStart + sectionSize.borderStart) < (this._scrollPosition + this._viewportSize)) {
                                return i;
                            }
                        }
                        return -1;
                    }
                },

                /// <field type="Function" locid="WinJS.UI.Hub.onheaderinvoked" helpKeyword="WinJS.UI.Hub.onheaderinvoked">
                /// Raised  when the user clicks on an interactive header.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                onheaderinvoked: createEvent(eventNames.headerInvoked),

                /// <field type="Function" locid="WinJS.UI.Hub.onloadingstatechanged" helpKeyword="WinJS.UI.Hub.onloadingstatechanged">
                /// Raised when the loadingState of the Hub changes.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                onloadingstatechanged: createEvent(eventNames.loadingStateChanged),

                /// <field type="Function" locid="WinJS.UI.Hub.oncontentanimating" helpKeyword="WinJS.UI.Hub.oncontentanimating">
                /// Raised when Hub is about to play entrance, contentTransition, insert, or remove animations.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                oncontentanimating: createEvent(eventNames.contentAnimating),

                _refresh: function hub_refresh() {
                    if (this._pendingRefresh) {
                        return;
                    }

                    this._loadId++;
                    this._setState(Hub.LoadingState.loading);
                    // This is to coalesce property setting operations such as sections and scrollPosition.
                    this._pendingRefresh = true;

                    Scheduler.schedule(this._refreshImpl.bind(this), Scheduler.Priority.high);
                },
                _refreshImpl: function hub_refreshImpl() {
                    if (this._disposed) {
                        return;
                    }

                    var fadeOutAnimation = Promise.wrap();
                    if (this._pendingSections) {
                        this._animateEntrance = true;
                        this._fireEntrance = !this._visible;
                        if (!this._fireEntrance) {
                            this._visible = false;
                            this._viewportElement.style.opacity = 0;

                            if (_TransitionAnimation.isAnimationEnabled()) {
                                var animateTransition = this._fireEvent(Hub._EventName.contentAnimating, {
                                    type: Hub.AnimationType.contentTransition
                                });

                                if (animateTransition) {
                                    this._viewportElement.style["-ms-overflow-style"] = "none";
                                    fadeOutAnimation = Animations.fadeOut(this._viewportElement).then(function () {
                                        this._viewportElement.style["-ms-overflow-style"] = "";
                                    }.bind(this));
                                }
                                this._animateEntrance = animateTransition;
                            }
                        }
                    }

                    fadeOutAnimation.done(this._applyProperties.bind(this));
                },
                _applyProperties: function hub_applyProperties() {
                    if (this._disposed) {
                        return;
                    }

                    this._pendingRefresh = false;

                    var needsToLoadSections = false;
                    if (this._pendingSections) {
                        needsToLoadSections = true;
                        this._updateEvents(this._sections, this._pendingSections);
                        this._sections = this._pendingSections;
                        this._pendingSections = null;
                        // Remove any declaratively specified hub sections before attachSections.
                        while (this.element.firstElementChild !== this._viewportElement) {
                            var toRemove = this.element.firstElementChild;
                            toRemove.parentNode.removeChild(toRemove);
                        }
                        _ElementUtilities.empty(this._surfaceElement);
                    }

                    if (this._pendingHeaderTemplate) {
                        this._headerTemplate = this._pendingHeaderTemplate;
                        this._pendingHeaderTemplate = null;
                    }

                    this._assignHeaderTemplate();

                    if (needsToLoadSections) {
                        this._attachSections();
                    }

                    // Scroll after headers are rendered and sections are attached so the scroll thumb is correct.
                    if (+this._pendingSectionOnScreen === this._pendingSectionOnScreen) {
                        // If there are both pending section on screen and scroll location use section on screen.
                        this.sectionOnScreen = this._pendingSectionOnScreen;
                    } else if (+this._pendingScrollLocation === this._pendingScrollLocation) {
                        this.scrollPosition = this._pendingScrollLocation;
                    } else {
                        // Sections reset without sectionOnScreen or scrollPosition APIs.
                        this.scrollPosition = 0;
                    }

                    this._pendingSectionOnScreen = null;
                    this._pendingScrollLocation = null;

                    // Using current (or new) scroll location load the sections
                    this._setState(Hub.LoadingState.loading);
                    this._loadSections();
                },
                _handleSectionChanged: function hub_handleSectionChanged(ev) {
                    // Change is triggered by binding list setAt() API.
                    if (this._pendingSections) {
                        return;
                    }

                    var newSection = ev.detail.newValue;
                    var oldSection = ev.detail.oldValue;
                    newSection._setHeaderTemplate(this.headerTemplate);
                    if (newSection.element !== oldSection.element) {
                        if (newSection.element.parentNode === this._surfaceElement) {
                            throw new _ErrorFromName("WinJS.UI.Hub.DuplicateSection", strings.duplicateSection);
                        }

                        this._surfaceElement.insertBefore(newSection.element, oldSection.element);
                        this._surfaceElement.removeChild(oldSection.element);
                        this._measured = false;

                        this._setState(Hub.LoadingState.loading);
                        this._loadSections();
                    }
                },
                _handleSectionInserted: function hub_handleSectionInserted(ev) {
                    // Insert is triggered by binding list insert APIs such as splice(), push(), and unshift().
                    if (this._pendingSections) {
                        return;
                    }

                    var index = ev.detail.index;
                    var section = ev.detail.value;

                    if (section._animation) {
                        section._animation.cancel();
                    }

                    var animation;
                    var result = this._fireEvent(Hub._EventName.contentAnimating, {
                        type: Hub.AnimationType.insert,
                        index: index,
                        section: section
                    });

                    if (result) {

                        var affectedElements = [];

                        for (var i = index + 1; i < this.sections.length; i++) {
                            affectedElements.push(this.sections.getAt(i).element);
                        }

                        animation = new Animations._createUpdateListAnimation([section.element], [], affectedElements);
                    }

                    if (section.element.parentNode === this._surfaceElement) {
                        throw new _ErrorFromName("WinJS.UI.Hub.DuplicateSection", strings.duplicateSection);
                    }

                    section._setHeaderTemplate(this.headerTemplate);
                    if (index < this.sections.length - 1) {
                        this._surfaceElement.insertBefore(section.element, this.sections.getAt(index + 1).element);
                    } else {
                        this._surfaceElement.appendChild(section.element);
                    }
                    this._measured = false;

                    if (animation) {
                        var insertAnimation = animation.execute();
                        this.runningAnimations = Promise.join([this.runningAnimations, insertAnimation]);
                    }

                    this._setState(Hub.LoadingState.loading);
                    this._loadSections();
                },
                _handleSectionMoved: function hub_handleSectionMoved(ev) {
                    // Move is triggered by binding list move() API.
                    if (this._pendingSections) {
                        return;
                    }

                    var newIndex = ev.detail.newIndex;
                    var section = ev.detail.value;

                    if (newIndex < this.sections.length - 1) {
                        this._surfaceElement.insertBefore(section.element, this.sections.getAt(newIndex + 1).element);
                    } else {
                        this._surfaceElement.appendChild(section.element);
                    }
                    this._measured = false;

                    this._setState(Hub.LoadingState.loading);
                    this._loadSections();
                },
                _handleSectionRemoved: function hub_handleSectionRemoved(ev) {
                    // Removed is triggered by binding list removal APIs such as splice(), pop(), and shift().
                    if (this._pendingSections) {
                        return;
                    }

                    var section = ev.detail.value;
                    var index = ev.detail.index;

                    var animationPromise = Promise.wrap();
                    var result = this._fireEvent(Hub._EventName.contentAnimating, {
                        type: Hub.AnimationType.remove,
                        index: index,
                        section: section
                    });

                    if (result) {
                        var affectedElements = [];

                        for (var i = index; i < this.sections.length; i++) {
                            affectedElements.push(this.sections.getAt(i).element);
                        }

                        var animation = new Animations._createUpdateListAnimation([], [section.element], affectedElements);

                        this._measure();
                        var offsetTop = section.element.offsetTop;
                        var offsetLeft = section.element.offsetLeft;
                        section.element.style.position = "absolute";
                        section.element.style.top = offsetTop;
                        section.element.style.left = offsetLeft;
                        section.element.style.opacity = 0;
                        this._measured = false;

                        animationPromise = animation.execute().then(function () {
                            section.element.style.position = "";
                            section.element.style.top = "";
                            section.element.style.left = "";
                            section.element.style.opacity = 1;
                        }.bind(this));
                    }

                    animationPromise.done(function () {
                        if (!this._disposed) {
                            this._surfaceElement.removeChild(section.element);
                            this._measured = false;
                        }
                    }.bind(this));

                    // Store animation promise in case it is inserted before remove animation finishes.
                    section._animation = animationPromise;
                    this.runningAnimations = Promise.join([this.runningAnimations, animationPromise]);

                    this._setState(Hub.LoadingState.loading);
                    this._loadSections();
                },
                _handleSectionReload: function hub_handleSectionReload() {
                    // Reload is triggered by large operations on the binding list such as reverse(). This causes
                    // _pendingSections to be true which ignores future insert/remove/modified/moved events until the new
                    // sections list is applied.
                    this.sections = this.sections;
                },
                _updateEvents: function hub_updateEvents(oldSections, newSections) {
                    if (oldSections) {
                        oldSections.removeEventListener("itemchanged", this._handleSectionChangedBind);
                        oldSections.removeEventListener("iteminserted", this._handleSectionInsertedBind);
                        oldSections.removeEventListener("itemmoved", this._handleSectionMovedBind);
                        oldSections.removeEventListener("itemremoved", this._handleSectionRemovedBind);
                        oldSections.removeEventListener("reload", this._handleSectionReloadBind);
                    }

                    if (newSections) {
                        newSections.addEventListener("itemchanged", this._handleSectionChangedBind);
                        newSections.addEventListener("iteminserted", this._handleSectionInsertedBind);
                        newSections.addEventListener("itemmoved", this._handleSectionMovedBind);
                        newSections.addEventListener("itemremoved", this._handleSectionRemovedBind);
                        newSections.addEventListener("reload", this._handleSectionReloadBind);
                    }
                },
                _attachSections: function hub_attachSections() {
                    this._measured = false;
                    for (var i = 0; i < this.sections.length; i++) {
                        var section = this._sections.getAt(i);
                        if (section._animation) {
                            section._animation.cancel();
                        }
                        if (section.element.parentNode === this._surfaceElement) {
                            throw new _ErrorFromName("WinJS.UI.Hub.DuplicateSection", strings.duplicateSection);
                        }
                        this._surfaceElement.appendChild(section.element);
                    }
                },
                _assignHeaderTemplate: function hub_assignHeaderTemplate() {
                    this._measured = false;
                    for (var i = 0; i < this.sections.length; i++) {
                        var section = this._sections.getAt(i);
                        section._setHeaderTemplate(this.headerTemplate);
                    }
                },
                _loadSection: function hub_loadSection(index) {
                    var section = this._sections.getAt(index);
                    return section._process().then(function resetVisibility() {
                        var style = section.contentElement.style;
                        if (style.visibility !== "") {
                            style.visibility = "";
                        }
                    });
                },
                _loadSections: function hub_loadSections() {
                    // Used to know if another load has interrupted this one.
                    this._loadId++;
                    var loadId = this._loadId;
                    var that = this;
                    var onScreenItemsAnimatedPromise = Promise.wrap();
                    var sectionIndicesToLoad = [];
                    var allSectionsLoadedPromise = Promise.wrap();

                    function loadNextSectionAfterPromise(promise) {
                        promise.then(function () {
                            Scheduler.schedule(loadNextSection, Scheduler.Priority.idle);
                        });
                    }

                    function loadNextSection() {
                        if (loadId === that._loadId && !that._disposed) {
                            if (sectionIndicesToLoad.length) {
                                var index = sectionIndicesToLoad.shift();
                                var loadedPromise = that._loadSection(index);
                                loadNextSectionAfterPromise(loadedPromise);
                            } else {
                                allSectionsLoadedSignal.complete();
                            }
                        }
                    }

                    if (!this._showProgressPromise) {
                        this._showProgressPromise = Promise.timeout(progressDelay).then(function () {
                            if (this._disposed) {
                                return;
                            }

                            if (!this._progressBar) {
                                this._progressBar = _Global.document.createElement("progress");
                                _ElementUtilities.addClass(this._progressBar, Hub._ClassName.hubProgress);
                                this._progressBar.max = 100;
                            }
                            if (!this._progressBar.parentNode) {
                                this.element.insertBefore(this._progressBar, this._viewportElement);
                            }
                            this._showProgressPromise = null;
                        }.bind(this), function () {
                            this._showProgressPromise = null;
                        }.bind(this));
                    }

                    if (this.sections.length) {
                        var allSectionsLoadedSignal = new _Signal();
                        allSectionsLoadedPromise = allSectionsLoadedSignal.promise;
                        // Synchronously load the sections on screen.
                        var synchronousProcessPromises = [];
                        var start = Math.max(0, this.indexOfFirstVisible);
                        var end = Math.max(0, this.indexOfLastVisible);
                        for (var i = start; i <= end; i++) {
                            synchronousProcessPromises.push(this._loadSection(i));
                        }

                        // Determine the order to load the rest of the sections.
                        start--;
                        end++;
                        while (start >= 0 || end < this.sections.length) {
                            if (end < this.sections.length) {
                                sectionIndicesToLoad.push(end);
                                end++;
                            }
                            if (start >= 0) {
                                sectionIndicesToLoad.push(start);
                                start--;
                            }
                        }

                        var onScreenSectionsLoadedPromise = Promise.join(synchronousProcessPromises);

                        // In case there are overlapping load calls
                        onScreenSectionsLoadedPromise.done(function () {
                            if (loadId === this._loadId && !that._disposed) {
                                if (this._showProgressPromise) {
                                    this._showProgressPromise.cancel();
                                }

                                if (this._progressBar && this._progressBar.parentNode) {
                                    this._progressBar.parentNode.removeChild(this._progressBar);
                                }

                                Scheduler.schedule(function Hub_entranceAnimation() {
                                    if (loadId === this._loadId && !that._disposed) {
                                        if (!this._visible) {
                                            this._visible = true;
                                            this._viewportElement.style.opacity = 1;

                                            if (this._animateEntrance && _TransitionAnimation.isAnimationEnabled()) {
                                                var eventDetail = {
                                                    type: Hub.AnimationType.entrance
                                                };

                                                if (!this._fireEntrance || this._fireEvent(Hub._EventName.contentAnimating, eventDetail)) {
                                                    this._viewportElement.style["-ms-overflow-style"] = "none";
                                                    onScreenItemsAnimatedPromise = Animations.enterContent(this._viewportElement, [{ left: this._fireEntrance ? "100px" : "40px", top: "0px", rtlflip: true }], { mechanism: "transition" }).then(function () {
                                                        this._viewportElement.style["-ms-overflow-style"] = "";
                                                    }.bind(this));
                                                }
                                            }
                                            if (this._element === _Global.document.activeElement) {
                                                this._moveFocusIn(this.sectionOnScreen);
                                            }
                                        }
                                    }
                                }, Scheduler.Priority.high, this, "WinJS.UI.Hub.entranceAnimation");
                            }
                        }.bind(this));

                        loadNextSectionAfterPromise(onScreenSectionsLoadedPromise);
                    } else {
                        if (this._showProgressPromise) {
                            this._showProgressPromise.cancel();
                        }

                        if (this._progressBar && this._progressBar.parentNode) {
                            this._progressBar.parentNode.removeChild(this._progressBar);
                        }
                    }

                    Promise.join([this.runningAnimations, onScreenItemsAnimatedPromise, allSectionsLoadedPromise]).done(function () {
                        if (loadId === this._loadId && !that._disposed) {
                            this.runningAnimations = Promise.wrap();
                            this._setState(Hub.LoadingState.complete);
                            Scheduler.schedule(this._updateSnapList.bind(this), Scheduler.Priority.idle);
                        }
                    }.bind(this));
                },
                /// <field type="String" hidden="true" locid="WinJS.UI.Hub.loadingState" helpKeyword="WinJS.UI.Hub.loadingState">
                /// Gets a value that indicates whether the Hub is still loading or whether
                /// loading is complete.  This property can return one of these values:
                /// "loading" or "complete".
                /// </field>
                loadingState: {
                    get: function () {
                        return this._loadingState;
                    }
                },
                _setState: function Hub_setState(state) {
                    if (state !== this._loadingState) {
                        this._writeProfilerMark("loadingStateChanged:" + state + ",info");
                        this._loadingState = state;
                        var eventObject = _Global.document.createEvent("CustomEvent");
                        eventObject.initCustomEvent(Hub._EventName.loadingStateChanged, true, false, { loadingState: state });
                        this._element.dispatchEvent(eventObject);
                    }
                },
                _parse: function hub_parse() {
                    var hubSections = [];
                    var hubSectionEl = this.element.firstElementChild;

                    while (hubSectionEl !== this._viewportElement) {
                        ControlProcessor.processAll(hubSectionEl);

                        var hubSectionContent = hubSectionEl.winControl;
                        if (hubSectionContent) {
                            hubSections.push(hubSectionContent);
                        } else {
                            throw new _ErrorFromName("WinJS.UI.Hub.InvalidContent", strings.invalidContent);
                        }

                        var nextSectionEl = hubSectionEl.nextElementSibling;
                        hubSectionEl = nextSectionEl;
                    }

                    this.sections = new BindingList.List(hubSections);
                },
                _fireEvent: function hub_fireEvent(type, detail) {
                    // Returns true if ev.preventDefault() was not called
                    var event = _Global.document.createEvent("CustomEvent");
                    event.initCustomEvent(type, true, true, detail);
                    return this.element.dispatchEvent(event);
                },

                _findHeaderTabStop: function hub_findHeaderTabStop(element) {
                    if (element.parentNode) {
                        if (_ElementUtilities._matchesSelector(element, ".win-hub-section-header-tabstop, .win-hub-section-header-tabstop *")) {
                            while (!_ElementUtilities.hasClass(element, "win-hub-section-header-tabstop")) {
                                element = element.parentElement;
                            }
                            return element;
                        }
                    }
                    return null;
                },
                _isHeaderInteractive: function hub_isHeaderInteractive(element) {
                    // Helper method to skip keyboarding and clicks with a header's sub interactive content
                    if (element.parentNode) {
                        return _ElementUtilities._matchesSelector(element, ".win-interactive, .win-interactive *");
                    }
                    return false;
                },
                _clickHandler: function hub_clickHandler(ev) {
                    var headerTabStopElement = this._findHeaderTabStop(ev.target);
                    if (headerTabStopElement && !this._isHeaderInteractive(ev.target)) {
                        var section = headerTabStopElement.parentElement.parentElement.winControl;
                        if (!section.isHeaderStatic) {
                            var sectionIndex = this.sections.indexOf(section);
                            this._fireEvent(Hub._EventName.headerInvoked, {
                                index: sectionIndex,
                                section: section
                            });
                        }
                    }
                },
                _resizeHandler: function hub_resizeHandler() {
                    // Viewport needs to be measured
                    this._measured = false;
                    Scheduler.schedule(this._updateSnapList.bind(this), Scheduler.Priority.idle);
                },
                _contentResizeHandler: function hub_contentResizeHandler() {
                    // Sections and scroll length need to be measured
                    this._measured = false;
                    Scheduler.schedule(this._updateSnapList.bind(this), Scheduler.Priority.idle);
                },
                _scrollHandler: function hub_scrollHandler() {
                    // Scroll location needs to be retrieved
                    this._measured = false;

                    if (this._pendingSections) {
                        return;
                    }

                    // Scroll events caused by users overwrite pending API modifications to scrollposition.
                    this._pendingScrollLocation = null;
                    this._pendingSectionOnScreen = null;

                    if (!this._pendingScrollHandler) {
                        this._pendingScrollHandler = _BaseUtils._requestAnimationFrame(function () {
                            this._pendingScrollHandler = null;

                            if (this._pendingSections) {
                                return;
                            }

                            if (this.loadingState !== Hub.LoadingState.complete) {
                                this._loadSections();
                            }
                        }.bind(this));
                    }
                },
                _measure: function hub_measure() {
                    // Any time a size changes (section growing, window resizing, etc) cachedSizes should be set to false
                    // and any time the variables need to be read again we should measure the variables. To avoid a lot of
                    // seperate layouts we measure the variables in a single batch.
                    if (!this._measured || this._scrollLength === 0) {
                        this._writeProfilerMark("measure,StartTM");
                        this._measured = true;

                        this._rtl = _Global.getComputedStyle(this._element, null).direction === "rtl";

                        if (this.orientation === _UI.Orientation.vertical) {
                            this._names = verticalNames;
                        } else {
                            if (this._rtl) {
                                this._names = rtlHorizontalNames;
                            } else {
                                this._names = ltrHorizontalNames;
                            }
                        }

                        this._viewportSize = this._viewportElement[this._names.offsetSize];
                        this._viewportOppositeSize = this._viewportElement[this._names.oppositeOffsetSize];
                        this._scrollPosition = _ElementUtilities.getScrollPosition(this._viewportElement)[this._names.scrollPos];
                        this._scrollLength = this._viewportElement[this._names.scrollSize];

                        var surfaceElementComputedStyle = _Global.getComputedStyle(this._surfaceElement);
                        this._startSpacer = parseFloat(surfaceElementComputedStyle[this._names.marginStart]) + parseFloat(surfaceElementComputedStyle[this._names.borderStart]) + parseFloat(surfaceElementComputedStyle[this._names.paddingStart]);
                        this._endSpacer = parseFloat(surfaceElementComputedStyle[this._names.marginEnd]) + parseFloat(surfaceElementComputedStyle[this._names.borderEnd]) + parseFloat(surfaceElementComputedStyle[this._names.paddingEnd]);

                        this._sectionSizes = [];
                        for (var i = 0; i < this.sections.length; i++) {
                            var section = this.sections.getAt(i);
                            var computedSectionStyle = _Global.getComputedStyle(section.element);
                            this._sectionSizes[i] = {
                                offset: section.element[this._names.offsetPos],
                                // Reminder: offsetWidth doesn't include margins and also rounds.
                                size: section.element[this._names.offsetSize],
                                marginStart: parseFloat(computedSectionStyle[this._names.marginStart]),
                                marginEnd: parseFloat(computedSectionStyle[this._names.marginEnd]),
                                borderStart: parseFloat(computedSectionStyle[this._names.borderStart]),
                                borderEnd: parseFloat(computedSectionStyle[this._names.borderEnd]),
                                paddingStart: parseFloat(computedSectionStyle[this._names.paddingStart]),
                                paddingEnd: parseFloat(computedSectionStyle[this._names.paddingEnd])
                            };

                            if (this._rtl && this.orientation === _UI.Orientation.horizontal) {
                                this._sectionSizes[i].offset = this._viewportSize - (this._sectionSizes[i].offset + this._sectionSizes[i].size);
                            }
                        }

                        this._writeProfilerMark("measure,StopTM");
                    }
                },
                _updateSnapList: function hub_updateSnapList() {
                    this._writeProfilerMark("updateSnapList,StartTM");
                    this._measure();

                    var snapList = "snapList(";
                    for (var i = 0; i < this._sectionSizes.length; i++) {
                        if (i > 0) {
                            snapList += ",";
                        }
                        var sectionSize = this._sectionSizes[i];
                        snapList += (sectionSize.offset - sectionSize.marginStart - this._startSpacer) + "px";
                    }
                    snapList += ")";

                    var snapListY = "";
                    var snapListX = "";
                    if (this.orientation === _UI.Orientation.vertical) {
                        snapListY = snapList;
                    } else {
                        snapListX = snapList;
                    }

                    if (this._lastSnapPointY !== snapListY) {
                        this._lastSnapPointY = snapListY;
                        this._viewportElement.style['-ms-scroll-snap-points-y'] = snapListY;
                    }

                    if (this._lastSnapPointX !== snapListX) {
                        this._lastSnapPointX = snapListX;
                        this._viewportElement.style['-ms-scroll-snap-points-x'] = snapListX;
                    }

                    this._writeProfilerMark("updateSnapList,StopTM");
                },
                _scrollToSection: function Hub_scrollToSection(index, withAnimation) {
                    this._measure();
                    var sectionSize = this._sectionSizes[index];
                    var scrollPositionToShowStartMargin = Math.min(this._scrollLength - this._viewportSize, sectionSize.offset - sectionSize.marginStart - this._startSpacer);

                    this._scrollTo(scrollPositionToShowStartMargin, withAnimation);
                },
                _ensureVisible: function hub_ensureVisible(index, withAnimation) {
                    this._measure();
                    var targetScrollPos = this._ensureVisibleMath(index, this._scrollPosition);
                    this._scrollTo(targetScrollPos, withAnimation);
                },
                _ensureVisibleMath: function hub_ensureVisibleMath(index, targetScrollPos) {
                    this._measure();
                    var sectionSize = this._sectionSizes[index];

                    var scrollPositionToShowStartMargin = Math.min(this._scrollLength - this._viewportSize, sectionSize.offset - sectionSize.marginStart - this._startSpacer);
                    var scrollPositionToShowEndMargin = Math.max(0, sectionSize.offset + sectionSize.size + sectionSize.marginEnd + this._endSpacer - this._viewportSize + 1);
                    if (targetScrollPos > scrollPositionToShowStartMargin) {
                        targetScrollPos = scrollPositionToShowStartMargin;
                    } else if (targetScrollPos < scrollPositionToShowEndMargin) {
                        targetScrollPos = Math.min(scrollPositionToShowStartMargin, scrollPositionToShowEndMargin);
                    }

                    return targetScrollPos;
                },
                _scrollTo: function hub_scrollTo(scrollPos, withAnimation) {
                    this._scrollPosition = scrollPos;
                    if (withAnimation) {
                        if (this.orientation === _UI.Orientation.vertical) {
                            _ElementUtilities._zoomTo(this._viewportElement, { contentX: 0, contentY: this._scrollPosition, viewportX: 0, viewportY: 0 });
                        } else {
                            _ElementUtilities._zoomTo(this._viewportElement, { contentX: this._scrollPosition, contentY: 0, viewportX: 0, viewportY: 0 });
                        }
                    } else {
                        var newScrollPos = {};
                        newScrollPos[this._names.scrollPos] = this._scrollPosition;
                        _ElementUtilities.setScrollPosition(this._viewportElement, newScrollPos);
                    }
                },
                _windowKeyDownHandler: function hub_windowKeyDownHandler(ev) {
                    // Include tab and shift tab. Note: Alt Key + Tab and Windows Key + Tab do not fire keydown with ev.key === "Tab".
                    if (ev.keyCode === Key.tab) {
                        this._tabSeenLast = true;

                        var that = this;
                        _BaseUtils._yieldForEvents(function () {
                            that._tabSeenLast = false;
                        });
                    }
                },
                _focusin: function hub_focusin(ev) {
                    // On focus we call ensureVisible to handle the tab or shift/tab to header. However if the
                    // focus was caused by a pointer down event we skip the focus.
                    if (this._tabSeenLast) {
                        var headerTabStopElement = this._findHeaderTabStop(ev.target);
                        if (headerTabStopElement && !this._isHeaderInteractive(ev.target)) {
                            var sectionIndex = this.sections.indexOf(headerTabStopElement.parentElement.parentElement.winControl);
                            if (sectionIndex > -1) {
                                this._ensureVisible(sectionIndex, true);
                            }
                        }
                    }

                    // Always remember the focused section for SemanticZoom.
                    var sectionElement = ev.target;
                    while (sectionElement && !_ElementUtilities.hasClass(sectionElement, _Section.HubSection._ClassName.hubSection)) {
                        sectionElement = sectionElement.parentElement;
                    }
                    if (sectionElement) {
                        var sectionIndex = this.sections.indexOf(sectionElement.winControl);
                        if (sectionIndex > -1) {
                            this._currentIndexForSezo = sectionIndex;
                        }
                    }

                    if (ev.target === this.element) {
                        var indexToFocus;
                        if (+this._sectionToFocus === this._sectionToFocus && this._sectionToFocus >= 0 && this._sectionToFocus < this.sections.length) {
                            indexToFocus = this._sectionToFocus;
                            this._sectionToFocus = null;
                        } else {
                            indexToFocus = this.sectionOnScreen;
                        }

                        this._moveFocusIn(indexToFocus);
                    }
                },
                _moveFocusIn: function hub_moveFocusIn(indexToFocus) {
                    if (indexToFocus >= 0) {
                        for (var i = indexToFocus; i < this.sections.length; i++) {
                            var section = this.sections.getAt(i);

                            var focusAttempt = _ElementUtilities._trySetActive(section._headerTabStopElement, this._viewportElement);

                            if (focusAttempt) {
                                return;
                            }

                            if (_ElementUtilities._setActiveFirstFocusableElement(section.contentElement, this._viewportElement)) {
                                return;
                            }
                        }

                        for (var i = indexToFocus - 1; i >= 0; i--) {
                            var section = this.sections.getAt(i);

                            if (_ElementUtilities._setActiveFirstFocusableElement(section.contentElement, this._viewportElement)) {
                                return;
                            }

                            var focusAttempt = _ElementUtilities._trySetActive(section._headerTabStopElement, this._viewportElement);

                            if (focusAttempt) {
                                return;
                            }
                        }
                    }
                },
                _keyDownHandler: function hub_keyDownHandler(ev) {
                    var leftKey = this._rtl ? Key.rightArrow : Key.leftArrow;
                    var rightKey = this._rtl ? Key.leftArrow : Key.rightArrow;

                        if (ev.keyCode === Key.upArrow || ev.keyCode === Key.downArrow || ev.keyCode === Key.leftArrow || ev.keyCode === Key.rightArrow || ev.keyCode === Key.pageUp || ev.keyCode === Key.pageDown) {
                        var headerTabStopElement = this._findHeaderTabStop(ev.target);
                        if (headerTabStopElement && !this._isHeaderInteractive(ev.target)) {
                            var currentSection = this.sections.indexOf(headerTabStopElement.parentElement.parentElement.winControl);
                            var targetSectionIndex;
                            var useEnsureVisible = false;
                            // Page up/down go to the next/previous header and line it up with the app header. Up/Right/Down/Left
                            // move focus to the next/previous header and move it on screen (app header distance from either edge).
                            if (ev.keyCode === Key.pageDown ||
                                (this.orientation === _UI.Orientation.horizontal && ev.keyCode === rightKey) ||
                                (this.orientation === _UI.Orientation.vertical && ev.keyCode === Key.downArrow)) {
                                // Do not include hidden headers.
                                for (var i = currentSection + 1; i < this.sections.length; i++) {
                                    if (this._tryFocus(i)) {
                                        targetSectionIndex = i;
                                        break;
                                    }
                                }
                            } else if (ev.keyCode === Key.pageUp ||
                                (this.orientation === _UI.Orientation.horizontal && ev.keyCode === leftKey) ||
                                (this.orientation === _UI.Orientation.vertical && ev.keyCode === Key.upArrow)) {
                                // Do not include hidden headers.
                                for (var i = currentSection - 1; i >= 0; i--) {
                                    if (this._tryFocus(i)) {
                                        targetSectionIndex = i;
                                        break;
                                    }
                                }
                            }
                            if (ev.keyCode === Key.upArrow || ev.keyCode === Key.downArrow || ev.keyCode === Key.leftArrow || ev.keyCode === Key.rightArrow) {
                                useEnsureVisible = true;
                            }

                            if (+targetSectionIndex === targetSectionIndex) {
                                if (useEnsureVisible) {
                                    this._ensureVisible(targetSectionIndex, true);
                                } else {
                                    this._scrollToSection(targetSectionIndex, true);
                                }
                            }

                            ev.preventDefault();
                        }
                    } else if (ev.keyCode === Key.home || ev.keyCode === Key.end) {
                        // Home/End scroll to start/end and leave focus where it is.
                        this._measure();
                        var maxScrollPos = Math.max(0, this._scrollLength - this._viewportSize);
                        this._scrollTo(ev.keyCode === Key.home ? 0 : maxScrollPos, true);
                        ev.preventDefault();
                    }
                },
                _tryFocus: function hub_tryFocus(index) {
                    var targetSection = this.sections.getAt(index);

                    _ElementUtilities._setActive(targetSection._headerTabStopElement, this._viewportElement);

                    return _Global.document.activeElement === targetSection._headerTabStopElement;
                },
                /// <field type="Object" locid="WinJS.UI.Hub.zoomableView" helpKeyword="WinJS.UI.Hub.zoomableView" isAdvanced="true">
                /// Gets a ZoomableView. This API supports the SemanticZoom infrastructure
                /// and is not intended to be used directly from your code.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                zoomableView: {
                    get: function zoomableView_get() {
                        if (!this._zoomableView) {
                            this._zoomableView = new ZoomableView(this);
                        }

                        return this._zoomableView;
                    }
                },
                _getPanAxis: function hub_getPanAxis() {
                    return this.orientation === _UI.Orientation.horizontal ? "horizontal" : "vertical";
                },
                _configureForZoom: function hub_configureForZoom() {
                    // Nothing to configure.
                },
                _setCurrentItem: function hub_setCurrentItem(x, y) {
                    var offset;
                    if (this.orientation === _UI.Orientation.horizontal) {
                        offset = x;
                    } else {
                        offset = y;
                    }

                    this._measure();
                    offset = offset + this._scrollPosition;
                    this._currentIndexForSezo = this._sectionSizes.length - 1;
                    for (var i = 1; i < this._sectionSizes.length; i++) {
                        var sectionSize = this._sectionSizes[i];
                        if (sectionSize.offset - sectionSize.marginStart > offset) {
                            this._currentIndexForSezo = i - 1;
                            break;
                        }
                    }
                },
                _getCurrentItem: function hub_getCurrentItem() {
                    var itemPosition;
                    if (this._sectionSizes.length > 0) {
                        this._measure();
                        var index = Math.max(0, Math.min(this._currentIndexForSezo, this._sectionSizes.length));
                        var sectionSize = this._sectionSizes[index];
                        if (this.orientation === _UI.Orientation.horizontal) {
                            itemPosition = {
                                left: Math.max(0, sectionSize.offset - sectionSize.marginStart - this._scrollPosition),
                                top: 0,
                                width: sectionSize.size,
                                height: this._viewportOppositeSize
                            };
                        } else {
                            itemPosition = {
                                left: 0,
                                top: Math.max(0, sectionSize.offset - sectionSize.marginStart - this._scrollPosition),
                                width: this._viewportOppositeSize,
                                height: sectionSize.size,
                            };
                        }

                        var section = this.sections.getAt(index);
                        // BUGBUG: 53301 ListView and Hub should document what they expect to be returned from the
                        // getCurrentItem so that positionItem apis line up. ListView zoomed out expects an object with
                        // groupIndexHint, groupKey, or groupDescription. Hub expects an object with index.
                        return Promise.wrap({ item: { data: section, index: index, groupIndexHint: index }, position: itemPosition });
                    }
                },
                _beginZoom: function hub_beginZoom() {
                    // Hide scroll thumb.
                    this._viewportElement.style["-ms-overflow-style"] = "none";
                },
                _positionItem: function hub_positionItem(item, position) {
                    if (item.index >= 0 && item.index < this._sectionSizes.length) {
                        this._measure();
                        var sectionSize = this._sectionSizes[item.index];

                        var offsetFromViewport;
                        if (this.orientation === _UI.Orientation.horizontal) {
                            offsetFromViewport = position.left;
                        } else {
                            offsetFromViewport = position.top;
                        }

                        this._sectionToFocus = item.index;

                        var targetScrollPosition = sectionSize.offset - offsetFromViewport;
                        // clamp section:
                        var targetScrollPosition = this._ensureVisibleMath(item.index, targetScrollPosition);

                        this._scrollPosition = targetScrollPosition;
                        var newScrollPos = {};
                        newScrollPos[this._names.scrollPos] = this._scrollPosition;
                        _ElementUtilities.setScrollPosition(this._viewportElement, newScrollPos);
                    }
                },
                _endZoom: function hub_endZoom() {
                    // Show scroll thumb.
                    this._viewportElement.style["-ms-overflow-style"] = "";
                },
                _writeProfilerMark: function hub_writeProfilerMark(text) {
                    var message = "WinJS.UI.Hub:" + this._id + ":" + text;
                    _WriteProfilerMark(message);
                    _Log.log && _Log.log(message, null, "hubprofiler");
                },
                dispose: function hub_dispose() {
                    /// <signature helpKeyword="WinJS.UI.Hub.dispose">
                    /// <summary locid="WinJS.UI.Hub.dispose">
                    /// Disposes this control.
                    /// </summary>
                    /// <compatibleWith platform="Windows" minVersion="8.1"/>
                    /// </signature>
                    if (this._disposed) {
                        return;
                    }
                    this._disposed = true;

                    _Global.removeEventListener('keydown', this._windowKeyDownHandlerBound);
                    _ElementUtilities._resizeNotifier.unsubscribe(this.element, this._resizeHandlerBound);

                    this._updateEvents(this._sections);

                    for (var i = 0; i < this.sections.length; i++) {
                        this.sections.getAt(i).dispose();
                    }
                }
            }, {
                /// <field locid="WinJS.UI.Hub.AnimationType" helpKeyword="WinJS.UI.Hub.AnimationType">
                /// Specifies whether the Hub animation is an entrance animation or a transition animation.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                AnimationType: {
                    /// <field locid="WinJS.UI.Hub.AnimationType.entrance" helpKeyword="WinJS.UI.Hub.AnimationType.entrance">
                    /// The animation plays when the Hub is first displayed.
                    /// <compatibleWith platform="Windows" minVersion="8.1"/>
                    /// </field>
                    entrance: "entrance",
                    /// <field locid="WinJS.UI.Hub.AnimationType.contentTransition" helpKeyword="WinJS.UI.Hub.AnimationType.contentTransition">
                    /// The animation plays when the Hub is changing its content.
                    /// <compatibleWith platform="Windows" minVersion="8.1"/>
                    /// </field>
                    contentTransition: "contentTransition",
                    /// <field locid="WinJS.UI.Hub.AnimationType.insert" helpKeyword="WinJS.UI.Hub.AnimationType.insert">
                    /// The animation plays when a section is inserted into the Hub.
                    /// <compatibleWith platform="Windows" minVersion="8.1"/>
                    /// </field>
                    insert: "insert",
                    /// <field locid="WinJS.UI.Hub.AnimationType.remove" helpKeyword="WinJS.UI.Hub.AnimationType.remove">
                    /// The animation plays when a section is removed into the Hub.
                    /// <compatibleWith platform="Windows" minVersion="8.1"/>
                    /// </field>
                    remove: "remove",
                },
                /// <field locid="WinJS.UI.Hub.LoadingState" helpKeyword="WinJS.UI.Hub.LoadingState">
                /// Gets the current loading state of the Hub.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                LoadingState: {
                    /// <field locid="WinJS.UI.Hub.LoadingState.loading" helpKeyword="WinJS.UI.Hub.LoadingState.loading">
                    /// The Hub is loading sections.
                    /// <compatibleWith platform="Windows" minVersion="8.1"/>
                    /// </field>
                    loading: "loading",
                    /// <field locid="WinJS.UI.Hub.LoadingState.complete" helpKeyword="WinJS.UI.Hub.LoadingState.complete">
                    /// All sections are loaded and animations are complete.
                    /// <compatibleWith platform="Windows" minVersion="8.1"/>
                    /// </field>
                    complete: "complete"
                },
                // Names of classes used by the Hub.
                _ClassName: {
                    hub: "win-hub",
                    hubSurface: "win-hub-surface",
                    hubProgress: "win-hub-progress",
                    hubViewport: "win-hub-viewport",
                    hubVertical: "win-hub-vertical",
                    hubHorizontal: "win-hub-horizontal",
                },
                // Names of events fired by the Hub.
                _EventName: {
                    contentAnimating: eventNames.contentAnimating,
                    headerInvoked: eventNames.headerInvoked,
                    loadingStateChanged: eventNames.loadingStateChanged
                },
            });

            _Base.Class.mix(Hub, _Control.DOMEventMixin);

            var ZoomableView = _Base.Class.define(function ZoomableView_ctor(hub) {
                this._hub = hub;
            }, {
                getPanAxis: function () {
                    return this._hub._getPanAxis();
                },
                configureForZoom: function (isZoomedOut, isCurrentView, triggerZoom, prefetchedPages) {
                    this._hub._configureForZoom(isZoomedOut, isCurrentView, triggerZoom, prefetchedPages);
                },
                setCurrentItem: function (x, y) {
                    this._hub._setCurrentItem(x, y);
                },
                getCurrentItem: function () {
                    return this._hub._getCurrentItem();
                },
                beginZoom: function () {
                    this._hub._beginZoom();
                },
                positionItem: function (item, position) {
                    return this._hub._positionItem(item, position);
                },
                endZoom: function (isCurrentView) {
                    this._hub._endZoom(isCurrentView);
                }
            });

            var strings = {
                get duplicateConstruction() { return "Invalid argument: Controls may only be instantiated one time for each DOM element"; },
                get duplicateSection() { return "Hub duplicate sections: Each HubSection must be unique"; },
                get invalidContent() { return "Invalid content: Hub content must be made up of HubSections."; },
                get hubViewportAriaLabel() { return _Resources._getWinJSString("ui/hubViewportAriaLabel").value; }
            };

            return Hub;
        })
    });

});
// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/AppBar/_Constants',[
     'exports',
     '../../Core/_Base',
], function appBarConstantsInit(exports, _Base) {
    "use strict";

    _Base.Namespace._moduleDefine(exports, null, {
        // AppBar class names.
        appBarClass: "win-appbar",
        firstDivClass: "win-firstdiv",
        finalDivClass: "win-finaldiv",
        invokeButtonClass: "win-appbar-invokebutton",
        ellipsisClass: "win-appbar-ellipsis",
        primaryCommandsClass: "win-primarygroup",
        secondaryCommandsClass: "win-secondarygroup",
        reducedClass: "win-reduced",
        commandLayoutClass: "win-commandlayout",
        topClass: "win-top",
        bottomClass: "win-bottom",
        showingClass : "win-appbar-showing",
        shownClass : "win-appbar-shown",
        hidingClass : "win-appbar-hiding",
        hiddenClass: "win-appbar-hidden",
        minimalClass: "win-appbar-minimal",

        // Constants for AppBar placement
        appBarPlacementTop: "top",
        appBarPlacementBottom: "bottom",

        // Constants for AppBar layouts
        appBarLayoutCustom: "custom",
        appBarLayoutCommands: "commands",

        // Constant for AppBar invokebutton width
        appBarInvokeButtonWidth: 60,

        // Constants for Commands
        typeSeparator: "separator",
        typeContent: "content",
        typeButton: "button",
        typeToggle: "toggle",
        typeFlyout: "flyout",
        menuCommandClass: "win-command",
        appBarCommandClass: "win-command",
        appBarCommandGlobalClass: "win-global",
        appBarCommandSelectionClass: "win-selection",
        sectionSelection: "selection",
        sectionGlobal: "global",

        // Prevents the element from showing a focus rect
        hideFocusClass: "win-hidefocus",

        // Other class names
        overlayClass: "win-overlay",
        flyoutClass: "win-flyout",
        flyoutLightClass: "win-ui-light",
        menuClass: "win-menu",
        menuToggleClass: "win-menu-toggle",
        settingsFlyoutClass: "win-settingsflyout",
        scrollsClass: "win-scrolls",

        // Constants for AppBarCommand full-size widths.
        separatorWidth: 60,
        buttonWidth: 100,

        narrowClass: "win-narrow",
        wideClass: "win-wide",
        _clickEatingAppBarClass: "win-appbarclickeater",
        _clickEatingFlyoutClass: "win-flyoutmenuclickeater",
        _visualViewportClass: "win-visualviewport-space",
    });
});
// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
/// <dictionary>animatable,appbar,appbars,divs,Flyout,Flyouts,iframe,Statics,unfocus,unselectable</dictionary>
define('WinJS/Controls/Flyout/_Overlay',[
    'exports',
    '../../Core/_Global',
    '../../Core/_WinRT',
    '../../Core/_Base',
    '../../Core/_BaseUtils',
    '../../Core/_ErrorFromName',
    '../../Core/_Events',
    '../../Core/_Resources',
    '../../Core/_WriteProfilerMark',
    '../../Animations',
    '../../ControlProcessor',
    '../../Promise',
    '../../Scheduler',
    '../../Utilities/_Control',
    '../../Utilities/_ElementUtilities',
    '../AppBar/_Constants'
], function overlayInit(exports, _Global, _WinRT, _Base, _BaseUtils, _ErrorFromName, _Events, _Resources, _WriteProfilerMark, Animations, ControlProcessor, Promise, Scheduler, _Control, _ElementUtilities, _Constants) {
    "use strict";

    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {
        _Overlay: _Base.Namespace._lazy(function () {
            var createEvent = _Events._createEventProperty;

            // Event Names
            var BEFORESHOW = "beforeshow";
            var AFTERSHOW = "aftershow";
            var BEFOREHIDE = "beforehide";
            var AFTERHIDE = "afterhide";

            // Helper to get DOM elements from input single object or array or IDs/toolkit/dom elements
            function _resolveElements(elements) {
                // No input is just an empty array
                if (!elements) {
                    return [];
                }

                // Make sure it's in array form.
                if (typeof elements === "string" || !elements || !elements.length) {
                    elements = [elements];
                }

                // Make sure we have a DOM element for each one, (could be string id name or toolkit object)
                var i,
                    realElements = [];
                for (i = 0; i < elements.length; i++) {
                    if (elements[i]) {
                        if (typeof elements[i] === "string") {
                            var element = _Global.document.getElementById(elements[i]);
                            if (element) {
                                realElements.push(element);
                            }
                        } else if (elements[i].element) {
                            realElements.push(elements[i].element);
                        } else {
                            realElements.push(elements[i]);
                        }
                    }
                }

                return realElements;
            }

            // Helpers for keyboard showing related events
            function _allOverlaysCallback(event, command) {
                var elements = _Global.document.querySelectorAll("." + _Constants.overlayClass);
                if (elements) {
                    var len = elements.length;
                    for (var i = 0; i < len; i++) {
                        var element = elements[i];
                        var control = element.winControl;
                        if (!control._disposed) {
                            if (control) {
                                control[command](event);
                            }
                        }
                    }
                }
            }

            function _edgyMayHideFlyouts() {
                // Flyouts and SettingsFlyouts should not light dismiss when they are the target of a right click.
                if (!_Overlay._rightMouseMightEdgy) {
                    _Overlay._hideAllFlyouts();
                }
            }

            var strings = {
                get duplicateConstruction() { return "Invalid argument: Controls may only be instantiated one time for each DOM element"; },
                get mustContainCommands() { return "Invalid HTML: AppBars/Menus must contain only AppBarCommands/MenuCommands"; },
                get closeOverlay() { return _Resources._getWinJSString("ui/closeOverlay").value; },
            };

            var _Overlay = _Base.Class.define(function _Overlay_ctor(element, options) {
                /// <signature helpKeyword="WinJS.UI._Overlay">
                /// <summary locid="WinJS.UI._Overlay">
                /// Constructs the Overlay control and associates it with the underlying DOM element.
                /// </summary>
                /// <param name="element" type="HTMLElement" domElement="true" locid="WinJS.UI._Overlay_p:element">
                /// The DOM element to be associated with the Overlay control.
                /// </param>
                /// <param name="options" type="Object" domElement="false" locid="WinJS.UI._Overlay_p:options">
                /// The set of options to be applied initially to the Overlay control.
                /// </param>
                /// <returns type="WinJS.UI._Overlay" locid="WinJS.UI._Overlay_returnValue">A fully constructed Overlay control.</returns>
                /// </signature>
                this._baseOverlayConstructor(element, options);
            }, {
                // Functions/properties
                _baseOverlayConstructor: function _Overlay_baseOverlayConstructor(element, options) {
                    this._disposed = false;

                    // Make sure there's an input element
                    if (!element) {
                        element = _Global.document.createElement("div");
                    }

                    // Check to make sure we weren't duplicated
                    var overlay = element.winControl;
                    if (overlay) {
                        throw new _ErrorFromName("WinJS.UI._Overlay.DuplicateConstruction", strings.duplicateConstruction);
                    }

                    if (!this._element) {
                        this._element = element;
                    }
                    this._sticky = false;
                    this._doNext = "";

                    this._element.style.visibility = "hidden";
                    this._element.style.opacity = 0;

                    // Remember ourselves
                    element.winControl = this;

                    // Attach our css class
                    _ElementUtilities.addClass(this._element, _Constants.overlayClass);
                    _ElementUtilities.addClass(this._element, "win-disposable");

                    // We don't want to be selectable, set UNSELECTABLE
                    var unselectable = this._element.getAttribute("unselectable");
                    if (unselectable === null || unselectable === undefined) {
                        this._element.setAttribute("unselectable", "on");
                    }

                    // Base animation is popIn/popOut
                    this._currentAnimateIn = this._baseAnimateIn;
                    this._currentAnimateOut = this._baseAnimateOut;
                    this._animationPromise = Promise.as();

                    // Command Animations to Queue
                    this._queuedToShow = [];
                    this._queuedToHide = [];
                    this._queuedCommandAnimation = false;

                    if (options) {
                        _Control.setOptions(this, options);
                    }
                },

                /// <field type="HTMLElement" domElement="true" readonly="true" hidden="true" locid="WinJS.UI._Overlay.element" helpKeyword="WinJS.UI._Overlay.element">The DOM element the Overlay is attached to</field>
                element: {
                    get: function () {
                        return this._element;
                    }
                },

                /// <field type="Boolean" locid="WinJS.UI._Overlay.disabled" helpKeyword="WinJS.UI._Overlay.disabled">Disable an Overlay, setting or getting the HTML disabled attribute.  When disabled the Overlay will no longer display with show(), and will hide if currently visible.</field>
                disabled: {
                    get: function () {
                        // Ensure it's a boolean because we're using the DOM element to keep in-sync
                        return !!this._element.disabled;
                    },
                    set: function (value) {
                        // Force this check into a boolean because our current state could be a bit confused since we tie to the DOM element
                        value = !!value;
                        var oldValue = !!this._element.disabled;
                        if (oldValue !== value) {
                            this._element.disabled = value;
                            if (!this.hidden && this._element.disabled) {
                                this._hideOrDismiss();
                            }
                        }
                    }
                },

                /// <field type="Function" locid="WinJS.UI._Overlay.onbeforeshow" helpKeyword="WinJS.UI._Overlay.onbeforeshow">
                /// Occurs immediately before the control is shown.
                /// </field>
                onbeforeshow: createEvent(BEFORESHOW),

                /// <field type="Function" locid="WinJS.UI._Overlay.onaftershow" helpKeyword="WinJS.UI._Overlay.onaftershow">
                /// Occurs immediately after the control is shown.
                /// </field>
                onaftershow: createEvent(AFTERSHOW),

                /// <field type="Function" locid="WinJS.UI._Overlay.onbeforehide" helpKeyword="WinJS.UI._Overlay.onbeforehide">
                /// Occurs immediately before the control is hidden.
                /// </field>
                onbeforehide: createEvent(BEFOREHIDE),

                /// <field type="Function" locid="WinJS.UI._Overlay.onafterhide" helpKeyword="WinJS.UI._Overlay.onafterhide">
                /// Occurs immediately after the control is hidden.
                /// </field>
                onafterhide: createEvent(AFTERHIDE),

                dispose: function () {
                    /// <signature helpKeyword="WinJS.UI.Overlay.dispose">
                    /// <summary locid="WinJS.UI.Overlay.dispose">
                    /// Disposes this Overlay.
                    /// </summary>
                    /// </signature>
                    if (this._disposed) {
                        return;
                    }

                    this._disposed = true;
                    this._dispose();
                },

                _dispose: function _Overlay_dispose() {
                    // To be overridden by subclasses
                },

                show: function () {
                    /// <signature helpKeyword="WinJS.UI._Overlay.show">
                    /// <summary locid="WinJS.UI._Overlay.show">
                    /// Shows the Overlay, if hidden, regardless of other state
                    /// </summary>
                    /// </signature>
                    // call private show to distinguish it from public version
                    this._show();
                },

                _show: function _Overlay_show() {
                    // We call our base _baseShow because AppBar may need to override show
                    this._baseShow();
                },

                hide: function () {
                    /// <signature helpKeyword="WinJS.UI._Overlay.hide">
                    /// <summary locid="WinJS.UI._Overlay.hide">
                    /// Hides the Overlay, if visible, regardless of other state
                    /// </summary>
                    /// </signature>
                    // call private hide to distinguish it from public version
                    this._hide();
                },

                _hide: function _Overlay_hide() {
                    // We call our base _baseHide because AppBar may need to override hide
                    this._baseHide();
                },

                // Is the overlay "hidden"?
                /// <field type="Boolean" hidden="true" locid="WinJS.UI._Overlay.hidden" helpKeyword="WinJS.UI._Overlay.hidden">Read only, true if an overlay is currently not visible.</field>
                hidden: {
                    get: function () {
                        return (this._element.style.visibility === "hidden" ||
                                this._element.winAnimating === "hiding" ||
                                this._doNext === "hide");
                    }
                },

                addEventListener: function (type, listener, useCapture) {
                    /// <signature helpKeyword="WinJS.UI._Overlay.addEventListener">
                    /// <summary locid="WinJS.UI._Overlay.addEventListener">
                    /// Add an event listener to the DOM element for this Overlay
                    /// </summary>
                    /// <param name="type" type="String" locid="WinJS.UI._Overlay.addEventListener_p:type">Required. Event type to add, "beforehide", "afterhide", "beforeshow", or "aftershow"</param>
                    /// <param name="listener" type="Function" locid="WinJS.UI._Overlay.addEventListener_p:listener">Required. The event handler function to associate with this event.</param>
                    /// <param name="useCapture" type="Boolean" locid="WinJS.UI._Overlay.addEventListener_p:useCapture">Optional. True, register for the event capturing phase.  False for the event bubbling phase.</param>
                    /// </signature>
                    return this._element.addEventListener(type, listener, useCapture);
                },

                removeEventListener: function (type, listener, useCapture) {
                    /// <signature helpKeyword="WinJS.UI._Overlay.removeEventListener">
                    /// <summary locid="WinJS.UI._Overlay.removeEventListener">
                    /// Remove an event listener to the DOM element for this Overlay
                    /// </summary>
                    /// <param name="type" type="String" locid="WinJS.UI._Overlay.removeEventListener_p:type">Required. Event type to remove, "beforehide", "afterhide", "beforeshow", or "aftershow"</param>
                    /// <param name="listener" type="Function" locid="WinJS.UI._Overlay.removeEventListener_p:listener">Required. The event handler function to associate with this event.</param>
                    /// <param name="useCapture" type="Boolean" locid="WinJS.UI._Overlay.removeEventListener_p:useCapture">Optional. True, register for the event capturing phase.  False for the event bubbling phase.</param>
                    /// </signature>
                    return this._element.removeEventListener(type, listener, useCapture);
                },

                _baseShow: function _Overlay_baseShow() {
                    // If we are already animating, just remember this for later
                    if (this._animating || this._needToHandleShowingKeyboard || this._needToHandleHidingKeyboard) {
                        this._doNext = "show";
                        return false;
                    }

                    // Each overlay tracks the size of the <HTML> element for triggering light-dismiss in the window resize handler.
                    this._cachedDocumentSize = this._cachedDocumentSize || _Overlay._sizeOfDocument();

                    // "hiding" would need to cancel.
                    if (this._element.style.visibility !== "visible") {
                        // Let us know we're showing.
                        this._element.winAnimating = "showing";

                        // Hiding, but not none
                        this._element.style.display = "";
                        this._element.style.visibility = "hidden";

                        // In case their event is going to manipulate commands, see if there are
                        // any queued command animations we can handle while we're still hidden.
                        if (this._queuedCommandAnimation) {
                            this._showAndHideFast(this._queuedToShow, this._queuedToHide);
                            this._queuedToShow = [];
                            this._queuedToHide = [];
                        }

                        // Send our "beforeShow" event
                        this._sendEvent(_Overlay.beforeShow);

                        // Need to measure
                        this._findPosition();

                        // Make sure it's visible, and fully opaque.
                        // Do the popup thing, sending event afterward.
                        var that = this;
                        this._animationPromise = this._currentAnimateIn().
                        then(function () {
                            that._baseEndShow();
                        }, function () {
                            that._baseEndShow();
                        });
                        return true;
                    }
                    return false;
                },

                // Flyout in particular will need to measure our positioning.
                _findPosition: function _Overlay_findPosition() {
                },

                _baseEndShow: function _Overlay_baseEndShow() {
                    if (this._disposed) {
                        return;
                    }

                    // Make sure it's visible after showing
                    this._element.setAttribute("aria-hidden", "false");

                    this._element.winAnimating = "";

                    // Do our derived classes show stuff
                    this._endShow();

                    // We're shown now
                    if (this._doNext === "show") {
                        this._doNext = "";
                    }

                    // After showing, send the after showing event
                    this._sendEvent(_Overlay.afterShow);
                    this._writeProfilerMark("show,StopTM"); // Overlay writes the stop profiler mark for all of its derived classes.

                    // If we had something queued, do that
                    Scheduler.schedule(this._checkDoNext, Scheduler.Priority.normal, this, "WinJS.UI._Overlay._checkDoNext");

                },

                _endShow: function _Overlay_endShow() {
                    // Nothing by default
                },

                _baseHide: function _Overlay_baseHide() {
                    // If we are already animating, just remember this for later
                    if (this._animating || this._needToHandleShowingKeyboard) {
                        this._doNext = "hide";
                        return false;
                    }

                    // In the unlikely event we're between the hiding keyboard and the resize events, just snap it away:
                    if (this._needToHandleHidingKeyboard) {
                        // use the "uninitialized" flag
                        this._element.style.visibility = "";
                    }

                    // "showing" would need to queue up.
                    if (this._element.style.visibility !== "hidden") {
                        // Let us know we're hiding, accessibility as well.
                        this._element.winAnimating = "hiding";
                        this._element.setAttribute("aria-hidden", "true");

                        // Send our "beforeHide" event
                        this._sendEvent(_Overlay.beforeHide);

                        // If our visibility is empty, then this is the first time, just hide it
                        if (this._element.style.visibility === "") {
                            // Initial hiding, just hide it
                            this._element.style.opacity = 0;
                            this._baseEndHide();
                        } else {
                            // Make sure it's hidden, and fully transparent.
                            var that = this;
                            this._animationPromise = this._currentAnimateOut().
                            then(function () {
                                that._baseEndHide();
                            }, function () {
                                that._baseEndHide();
                            });
                        }
                        return true;
                    }

                    return false;
                },

                _baseEndHide: function _Overlay_baseEndHide() {
                    if (this._disposed) {
                        return;
                    }

                    // Make sure animation is finished.
                    this._element.style.visibility = "hidden";
                    this._element.style.display = "none";
                    this._element.winAnimating = "";

                    // In case their event is going to manipulate commands, see if there
                    // are any queued command animations we can handle now we're hidden.
                    if (this._queuedCommandAnimation) {
                        this._showAndHideFast(this._queuedToShow, this._queuedToHide);
                        this._queuedToShow = [];
                        this._queuedToHide = [];
                    }

                    // We're hidden now
                    if (this._doNext === "hide") {
                        this._doNext = "";
                    }

                    // After hiding, send our "afterHide" event
                    this._sendEvent(_Overlay.afterHide);
                    this._writeProfilerMark("hide,StopTM"); // Overlay writes the stop profiler mark for all of its derived classes.


                    // If we had something queued, do that.  This has to be after
                    // the afterHide event in case it triggers a show() and they
                    // have something to do in beforeShow that requires afterHide first.
                    Scheduler.schedule(this._checkDoNext, Scheduler.Priority.normal, this, "WinJS.UI._Overlay._checkDoNext");
                },

                _checkDoNext: function _Overlay_checkDoNext() {
                    // Do nothing if we're still animating
                    if (this._animating || this._needToHandleShowingKeyboard || this._needToHandleHidingKeyboard || this._disposed) {
                        return;
                    }

                    if (this._doNext === "hide") {
                        // Do hide first because animating commands would be easier
                        this._hide();
                        this._doNext = "";
                    } else if (this._queuedCommandAnimation) {
                        // Do queued commands before showing if possible
                        this._showAndHideQueue();
                    } else if (this._doNext === "show") {
                        // Show last so that we don't unnecessarily animate commands
                        this._show();
                        this._doNext = "";
                    }
                },

                // Default animations
                _baseAnimateIn: function _Overlay_baseAnimateIn() {
                    this._element.style.opacity = 0;
                    this._element.style.visibility = "visible";
                    // touch opacity so that IE fades from the 0 we just set to 1
                    _Global.getComputedStyle(this._element, null).opacity;
                    return Animations.fadeIn(this._element);
                },

                _baseAnimateOut: function _Overlay_baseAnimateOut() {
                    this._element.style.opacity = 1;
                    // touch opacity so that IE fades from the 1 we just set to 0
                    _Global.getComputedStyle(this._element, null).opacity;
                    return Animations.fadeOut(this._element);
                },

                _animating: {
                    get: function _Overlay_animating_get() {
                        // Ensure it's a boolean because we're using the DOM element to keep in-sync
                        return !!this._element.winAnimating;
                    }
                },

                // Send one of our events
                _sendEvent: function _Overlay_sendEvent(eventName, detail) {
                    if (this._disposed) {
                        return;
                    }
                    var event = _Global.document.createEvent("CustomEvent");
                    event.initEvent(eventName, true, true, (detail || {}));
                    this._element.dispatchEvent(event);
                },

                // Show commands
                _showCommands: function _Overlay_showCommands(commands, immediate) {
                    var showHide = this._resolveCommands(commands);
                    this._showAndHideCommands(showHide.commands, [], immediate);
                },

                // Hide commands
                _hideCommands: function _Overlay_hideCommands(commands, immediate) {
                    var showHide = this._resolveCommands(commands);
                    this._showAndHideCommands([], showHide.commands, immediate);
                },

                // Hide commands
                _showOnlyCommands: function _Overlay_showOnlyCommands(commands, immediate) {
                    var showHide = this._resolveCommands(commands);
                    this._showAndHideCommands(showHide.commands, showHide.others, immediate);
                },

                _showAndHideCommands: function _Overlay_showAndHideCommands(showCommands, hideCommands, immediate) {
                    // Immediate is "easy"
                    if (immediate || (this.hidden && !this._animating)) {
                        // Immediate mode (not animated)
                        this._showAndHideFast(showCommands, hideCommands);
                        // Need to remove them from queues, but others could be queued
                        this._removeFromQueue(showCommands, this._queuedToShow);
                        this._removeFromQueue(hideCommands, this._queuedToHide);
                    } else {

                        // Queue Commands
                        this._updateAnimateQueue(showCommands, this._queuedToShow, this._queuedToHide);
                        this._updateAnimateQueue(hideCommands, this._queuedToHide, this._queuedToShow);
                    }
                },

                _removeFromQueue: function _Overlay_removeFromQueue(commands, queue) {
                    // remove commands from queue.
                    var count;
                    for (count = 0; count < commands.length; count++) {
                        // Remove if it was in queue
                        var countQ;
                        for (countQ = 0; countQ < queue.length; countQ++) {
                            if (queue[countQ] === commands[count]) {
                                queue.splice(countQ, 1);
                                break;
                            }
                        }
                    }
                },

                _updateAnimateQueue: function _Overlay_updateAnimateQueue(addCommands, toQueue, fromQueue) {
                    if (this._disposed) {
                        return;
                    }

                    // Add addCommands to toQueue and remove addCommands from fromQueue.
                    var count;
                    for (count = 0; count < addCommands.length; count++) {
                        // See if it's already in toQueue
                        var countQ;
                        for (countQ = 0; countQ < toQueue.length; countQ++) {
                            if (toQueue[countQ] === addCommands[count]) {
                                break;
                            }
                        }
                        if (countQ === toQueue.length) {
                            // Not found, add it
                            toQueue[countQ] = addCommands[count];
                        }
                        // Remove if it was in fromQueue
                        for (countQ = 0; countQ < fromQueue.length; countQ++) {
                            if (fromQueue[countQ] === addCommands[count]) {
                                fromQueue.splice(countQ, 1);
                                break;
                            }
                        }
                    }
                    // If we haven't queued the actual animation
                    if (!this._queuedCommandAnimation) {
                        // If not already animating, we'll need to call _checkDoNext
                        if (!this._animating) {
                            Scheduler.schedule(this._checkDoNext, Scheduler.Priority.normal, this, "WinJS.UI._Overlay._checkDoNext");
                        }
                        this._queuedCommandAnimation = true;
                    }
                },

                // show/hide commands without doing any animation.
                _showAndHideFast: function _Overlay_showAndHideFast(showCommands, hideCommands) {
                    var count;
                    var command;
                    for (count = 0; count < showCommands.length; count++) {
                        command = showCommands[count];
                        if (command && command.style) {
                            command.style.visibility = "";
                            command.style.display = "";
                        }
                    }
                    for (count = 0; count < hideCommands.length; count++) {
                        command = hideCommands[count];
                        if (command && command.style) {
                            command.style.visibility = "hidden";
                            command.style.display = "none";
                        }
                    }

                    this._commandsUpdated();

                },

                // show and hide the queued commands, perhaps animating if overlay isn't hidden.
                _showAndHideQueue: function _Overlay_showAndHideQueue() {
                    // Only called if not currently animating.
                    // We'll be done with the queued stuff when we return.
                    this._queuedCommandAnimation = false;

                    // Shortcut if hidden
                    if (this.hidden) {
                        this._showAndHideFast(this._queuedToShow, this._queuedToHide);
                        // Might be something else to do
                        Scheduler.schedule(this._checkDoNext, Scheduler.Priority.normal, this, "WinJS.UI._Overlay._checkDoNext");
                    } else {
                        // Animation has 3 parts:  "hiding", "showing", and "moving"
                        // PVL has "addToList" and "deleteFromList", both of which allow moving parts.
                        // So we'll set up "add" for showing, and use "delete" for "hiding" + moving,
                        // then trigger both at the same time.
                        var showCommands = this._queuedToShow;
                        var hideCommands = this._queuedToHide;
                        var siblings = this._findSiblings(showCommands.concat(hideCommands));

                        // Filter out the commands queued for animation that don't need to be animated.
                        var count;
                        for (count = 0; count < showCommands.length; count++) {
                            // If this one's not real or not attached, skip it
                            if (!showCommands[count] ||
                                !showCommands[count].style ||
                                !_Global.document.body.contains(showCommands[count])) {
                                // Not real, skip it
                                showCommands.splice(count, 1);
                                count--;
                            } else if (showCommands[count].style.visibility !== "hidden" && showCommands[count].style.opacity !== "0") {
                                // Don't need to animate showing this one, already visible, so now it's a sibling
                                siblings.push(showCommands[count]);
                                showCommands.splice(count, 1);
                                count--;
                            }
                        }
                        for (count = 0; count < hideCommands.length; count++) {
                            // If this one's not real or not attached, skip it
                            if (!hideCommands[count] ||
                                !hideCommands[count].style ||
                                !_Global.document.body.contains(hideCommands[count]) ||
                                hideCommands[count].style.visibility === "hidden" ||
                                hideCommands[count].style.opacity === "0") {
                                // Don't need to animate hiding this one, not real, or it's hidden,
                                // so don't even need it as a sibling.
                                hideCommands.splice(count, 1);
                                count--;
                            }
                        }

                        // Start command animations.
                        var commandsAnimationPromise = this._baseBeginAnimateCommands(showCommands, hideCommands, siblings);

                        // Hook end animations
                        var that = this;
                        if (commandsAnimationPromise) {
                            // Needed to animate
                            commandsAnimationPromise.done(
                                function () { that._baseEndAnimateCommands(hideCommands); },
                                function () { that._baseEndAnimateCommands(hideCommands); }
                                );
                        } else {
                            // Already positioned correctly
                            Scheduler.schedule(function Overlay_async_baseEndAnimationCommands() { that._baseEndAnimateCommands([]); },
                                Scheduler.Priority.normal, null,
                                "WinJS.UI._Overlay._endAnimateCommandsWithoutAnimation");
                        }
                    }

                    // Done, clear queues
                    this._queuedToShow = [];
                    this._queuedToHide = [];
                },

                _baseBeginAnimateCommands: function _Overlay_baseBeginAnimateCommands(showCommands, hideCommands, siblings) {
                    // The parameters are 3 mutually exclusive arrays of win-command elements contained in this Overlay.
                    // 1) showCommands[]: All of the HIDDEN win-command elements that ARE scheduled to show.
                    // 2) hideCommands[]: All of the VISIBLE win-command elements that ARE shceduled to hide.
                    // 3) siblings[]: i. All VISIBLE win-command elements that ARE NOT scheduled to hide.
                    //               ii. All HIDDEN win-command elements that ARE NOT scheduled to hide OR show.
                    this._beginAnimateCommands(showCommands, hideCommands, this._getVisibleCommands(siblings));

                    var showAnimated = null,
                        hideAnimated = null;

                    // Hide commands first, with siblings if necessary,
                    // so that the showing commands don't disrupt the hiding commands position.
                    if (hideCommands.length > 0) {
                        hideAnimated = Animations.createDeleteFromListAnimation(hideCommands, showCommands.length === 0 ? siblings : undefined);
                    }
                    if (showCommands.length > 0) {
                        showAnimated = Animations.createAddToListAnimation(showCommands, siblings);
                    }

                    // Update hiding commands
                    for (var count = 0, len = hideCommands.length; count < len; count++) {
                        // Need to fix our position
                        var rectangle = hideCommands[count].getBoundingClientRect(),
                            style = _Global.getComputedStyle(hideCommands[count]);

                        // Use the bounding box, adjusting for margins
                        hideCommands[count].style.top = (rectangle.top - parseFloat(style.marginTop)) + "px";
                        hideCommands[count].style.left = (rectangle.left - parseFloat(style.marginLeft)) + "px";
                        hideCommands[count].style.opacity = 0;
                        hideCommands[count].style.position = "fixed";
                    }

                    // Mark as animating
                    this._element.winAnimating = "rearranging";

                    // Start hiding animations
                    // Hide needs extra cleanup when done
                    var promise = null;
                    if (hideAnimated) {
                        promise = hideAnimated.execute();
                    }

                    // Update showing commands,
                    // After hiding commands so that the hiding ones fade in the right place.
                    for (count = 0; count < showCommands.length; count++) {
                        showCommands[count].style.visibility = "";
                        showCommands[count].style.display = "";
                        showCommands[count].style.opacity = 1;
                    }

                    // Start showing animations
                    if (showAnimated) {
                        var newPromise = showAnimated.execute();
                        if (promise) {
                            promise = Promise.join([promise, newPromise]);
                        } else {
                            promise = newPromise;
                        }
                    }

                    return promise;
                },

                _beginAnimateCommands: function _Overlay_beginAnimateCommands() {
                    // Nothing by default
                },

                _getVisibleCommands: function _Overlay_getVisibleCommands(commandSubSet) {
                    var command,
                        commands = commandSubSet,
                        visibleCommands = [];

                    if (!commands) {
                        // Crawl the inner HTML for the commands.
                        commands = this.element.querySelectorAll(".win-command");
                    }

                    for (var i = 0, len = commands.length; i < len; i++) {
                        command = commands[i].winControl || commands[i];
                        if (!command.hidden) {
                            visibleCommands.push(command);
                        }
                    }

                    return visibleCommands;
                },

                // Once animation is complete, ensure that the commands are display:none
                // and check if there's another animation to start.
                _baseEndAnimateCommands: function _Overlay_baseEndAnimateCommands(hideCommands) {
                    if (this._disposed) {
                        return;
                    }

                    // Update us
                    var count;
                    for (count = 0; count < hideCommands.length; count++) {
                        // Force us back into our appbar so that we can show again correctly
                        hideCommands[count].style.position = "";
                        hideCommands[count].getBoundingClientRect();
                        // Now make us really hidden
                        hideCommands[count].style.visibility = "hidden";
                        hideCommands[count].style.display = "none";
                        hideCommands[count].style.opacity = 1;
                    }
                    // Done animating
                    this._element.winAnimating = "";

                    this._endAnimateCommands();

                    // Might be something else to do
                    this._checkDoNext();
                },

                _endAnimateCommands: function _Overlay_endAnimateCommands() {
                    // Nothing by default
                },

                // Resolves our commands
                _resolveCommands: function _Overlay_resolveCommands(commands) {
                    // First make sure they're all DOM elements.
                    commands = _resolveElements(commands);

                    // Now make sure they're all in this container
                    var result = {};
                    result.commands = [];
                    result.others = [];
                    var allCommands = this.element.querySelectorAll(".win-command");
                    var countAll, countIn;
                    for (countAll = 0; countAll < allCommands.length; countAll++) {
                        var found = false;
                        for (countIn = 0; countIn < commands.length; countIn++) {
                            if (commands[countIn] === allCommands[countAll]) {
                                result.commands.push(allCommands[countAll]);
                                commands.splice(countIn, 1);
                                found = true;
                                break;
                            }
                        }
                        if (!found) {
                            result.others.push(allCommands[countAll]);
                        }
                    }
                    return result;
                },

                // Find siblings, all DOM elements now.
                // Returns all .win-commands in this Overlay that are NOT in the passed in 'commands' array.
                _findSiblings: function _Overlay_findSiblings(commands) {
                    // Now make sure they're all in this container
                    var siblings = [];
                    var allCommands = this.element.querySelectorAll(".win-command");
                    var countAll, countIn;
                    for (countAll = 0; countAll < allCommands.length; countAll++) {
                        var found = false;
                        for (countIn = 0; countIn < commands.length; countIn++) {
                            if (commands[countIn] === allCommands[countAll]) {
                                commands.splice(countIn, 1);
                                found = true;
                                break;
                            }
                        }
                        if (!found) {
                            siblings.push(allCommands[countAll]);
                        }
                    }
                    return siblings;
                },

                _baseResize: function _Overlay_baseResize(event) {
                    // Avoid the cost of a resize if the Overlay is hidden.
                    if (this._cachedDocumentSize) {
                        if (this.hidden) {
                            this._cachedDocumentSize = null;
                        } else {
                            // Overlays will light dismiss on <HTML> resize.
                            var newDocSize = _Overlay._sizeOfDocument();
                            if (this._cachedDocumentSize.width !== newDocSize.width || this._cachedDocumentSize.height !== newDocSize.height) {
                                this._cachedDocumentSize = newDocSize;
                                if (!this._sticky) {
                                    this._hideOrDismiss();
                                }
                            }
                        }
                    }

                    // Call specific resize
                    this._resize(event);
                },

                _hideOrDismiss: function _Overlay_hideOrDismiss() {
                    var element = this._element;
                    if (element && _ElementUtilities.hasClass(element, _Constants.settingsFlyoutClass)) {
                        this._dismiss();
                    } else {
                        this.hide();
                    }
                },

                _resize: function _Overlay_resize() {
                    // Nothing by default
                },

                _commandsUpdated: function _Overlay_commandsUpdated() {
                    // Nothing by default
                },

                _checkScrollPosition: function _Overlay_checkScrollPosition() {
                    // Nothing by default
                },

                _showingKeyboard: function _Overlay_showingKeyboard() {
                    // Nothing by default
                },

                _hidingKeyboard: function _Overlay_hidingKeyboard() {
                    // Nothing by default
                },

                // Verify that this HTML AppBar only has AppBar/MenuCommands.
                _verifyCommandsOnly: function _Overlay_verifyCommandsOnly(element, type) {
                    var children = element.children;
                    var commands = new Array(children.length);
                    for (var i = 0; i < children.length; i++) {
                        // If constructed they have win-command class, otherwise they have data-win-control
                        if (!_ElementUtilities.hasClass(children[i], "win-command") &&
                        children[i].getAttribute("data-win-control") !== type) {
                            // Wasn't tagged with class or AppBar/MenuCommand, not an AppBar/MenuCommand
                            throw new _ErrorFromName("WinJS.UI._Overlay.MustContainCommands", strings.mustContainCommands);
                        } else {
                            // Instantiate the commands.
                            ControlProcessor.processAll(children[i]);
                            commands[i] = children[i].winControl;
                        }
                    }
                    return commands;
                },

                // Sets focus on what we think is the last tab stop. If nothing is focusable will
                // try to set focus on itself.
                _focusOnLastFocusableElementOrThis: function _Overlay_focusOnLastFocusableElementOrThis() {
                    if (!this._focusOnLastFocusableElement()) {
                        // Nothing is focusable.  Set focus to this.
                        _Overlay._trySetActive(this._element);
                    }
                },

                // Sets focus to what we think is the last tab stop. This element must have
                // a firstDiv with tabIndex equal to the lowest tabIndex in the element
                // and a finalDiv with tabIndex equal to the highest tabIndex in the element.
                // Also the firstDiv must be its first child and finalDiv be its last child.
                // Returns true if successful, false otherwise.
                _focusOnLastFocusableElement: function _Overlay_focusOnLastFocusableElement() {
                    if (this._element.firstElementChild) {
                        var oldFirstTabIndex = this._element.firstElementChild.tabIndex;
                        var oldLastTabIndex = this._element.lastElementChild.tabIndex;
                        this._element.firstElementChild.tabIndex = -1;
                        this._element.lastElementChild.tabIndex = -1;

                        var tabResult = _ElementUtilities._focusLastFocusableElement(this._element);

                        if (tabResult) {
                            _Overlay._trySelect(_Global.document.activeElement);
                        }

                        this._element.firstElementChild.tabIndex = oldFirstTabIndex;
                        this._element.lastElementChild.tabIndex = oldLastTabIndex;

                        return tabResult;
                    } else {
                        return false;
                    }
                },


                // Sets focus on what we think is the first tab stop. If nothing is focusable will
                // try to set focus on itself.
                _focusOnFirstFocusableElementOrThis: function _Overlay_focusOnFirstFocusableElementOrThis() {
                    if (!this._focusOnFirstFocusableElement()) {
                        // Nothing is focusable.  Set focus to this.
                        _Overlay._trySetActive(this._element);
                    }
                },

                // Sets focus to what we think is the first tab stop. This element must have
                // a firstDiv with tabIndex equal to the lowest tabIndex in the element
                // and a finalDiv with tabIndex equal to the highest tabIndex in the element.
                // Also the firstDiv must be its first child and finalDiv be its last child.
                // Returns true if successful, false otherwise.
                _focusOnFirstFocusableElement: function _Overlay__focusOnFirstFocusableElement() {
                    if (this._element.firstElementChild) {
                        var oldFirstTabIndex = this._element.firstElementChild.tabIndex;
                        var oldLastTabIndex = this._element.lastElementChild.tabIndex;
                        this._element.firstElementChild.tabIndex = -1;
                        this._element.lastElementChild.tabIndex = -1;

                        var tabResult = _ElementUtilities._focusFirstFocusableElement(this._element);

                        if (tabResult) {
                            _Overlay._trySelect(_Global.document.activeElement);
                        }

                        this._element.firstElementChild.tabIndex = oldFirstTabIndex;
                        this._element.lastElementChild.tabIndex = oldLastTabIndex;

                        return tabResult;
                    } else {
                        return false;
                    }
                },

                _addOverlayEventHandlers: function _Overlay_addOverlayEventHandlers(isFlyoutOrSettingsFlyout) {
                    // Set up global event handlers for all overlays
                    if (!_Overlay._flyoutEdgeLightDismissEvent) {
                        // Dismiss on blur & resize
                        // Focus handlers generally use WinJS.Utilities._addEventListener with focusout/focusin. This
                        // uses the browser's blur event directly beacuse _addEventListener doesn't support focusout/focusin
                        // on window.
                        _Global.addEventListener("blur", _Overlay._checkBlur, false);

                        var that = this;

                        // Be careful so it behaves in designer as well.
                        if (_WinRT.Windows.UI.Input.EdgeGesture) {
                            // Catch edgy events too
                            var commandUI = _WinRT.Windows.UI.Input.EdgeGesture.getForCurrentView();
                            commandUI.addEventListener("starting", _Overlay._hideAllFlyouts);
                            commandUI.addEventListener("completed", _edgyMayHideFlyouts);
                        }

                        if (_WinRT.Windows.UI.ViewManagement.InputPane) {
                            // React to Soft Keyboard events
                            var inputPane = _WinRT.Windows.UI.ViewManagement.InputPane.getForCurrentView();
                            inputPane.addEventListener("showing", function (event) {
                                that._writeProfilerMark("_showingKeyboard,StartTM");
                                _allOverlaysCallback(event, "_showingKeyboard");
                                that._writeProfilerMark("_showingKeyboard,StopTM");
                            });
                            inputPane.addEventListener("hiding", function (event) {
                                that._writeProfilerMark("_hidingKeyboard,StartTM");
                                _allOverlaysCallback(event, "_hidingKeyboard");
                                that._writeProfilerMark("_hidingKeyboard,StopTM");
                            });
                            // Document scroll event
                            _Global.document.addEventListener("scroll", function (event) {
                                that._writeProfilerMark("_checkScrollPosition,StartTM");
                                _allOverlaysCallback(event, "_checkScrollPosition");
                                that._writeProfilerMark("_checkScrollPosition,StopTM");
                            });
                        }

                        // Window resize event
                        _Global.addEventListener("resize", function (event) {
                            that._writeProfilerMark("_baseResize,StartTM");
                            _allOverlaysCallback(event, "_baseResize");
                            that._writeProfilerMark("_baseResize,StopTM");
                        });

                        _Overlay._flyoutEdgeLightDismissEvent = true;
                    }

                    // Individual handlers for Flyouts only
                    if (isFlyoutOrSettingsFlyout) {
                        this._handleEventsForFlyoutOrSettingsFlyout();
                    }
                },

                _handleEventsForFlyoutOrSettingsFlyout: function _Overlay_handleEventsForFlyoutOrSettingsFlyout() {
                    var that = this;
                    // Need to hide ourselves if we lose focus
                    _ElementUtilities._addEventListener(this._element, "focusout", function (e) { _Overlay._hideIfLostFocus(that, e); }, false);

                    // Attempt to flag right clicks that may turn into edgy
                    _ElementUtilities._addEventListener(this._element, "pointerdown", _Overlay._checkRightClickDown, true);
                    _ElementUtilities._addEventListener(this._element, "pointerup", _Overlay._checkRightClickUp, true);
                },

                _writeProfilerMark: function _Overlay_writeProfilerMark(text) {
                    _WriteProfilerMark("WinJS.UI._Overlay:" + this._id + ":" + text);
                }
            },
            {
                // Statics
                _clickEatingAppBarDiv: false,
                _clickEatingFlyoutDiv: false,
                _flyoutEdgeLightDismissEvent: false,

                _hideFlyouts: function (testElement, notSticky) {
                    var elements = testElement.querySelectorAll("." + _Constants.flyoutClass);
                    var len = elements.length;
                    for (var i = 0; i < len; i++) {
                        var element = elements[i];
                        if (element.style.visibility !== "hidden") {
                            var flyout = element.winControl;
                            if (flyout && (!notSticky || !flyout._sticky)) {
                                flyout._hideOrDismiss();
                            }
                        }
                    }
                },

                _hideSettingsFlyouts: function (testElement, notSticky) {
                    var elements = testElement.querySelectorAll("." + _Constants.settingsFlyoutClass);
                    var len = elements.length;
                    for (var i = 0; i < len; i++) {
                        var element = elements[i];
                        if (element.style.visibility !== "hidden") {
                            var settingsFlyout = element.winControl;
                            if (settingsFlyout && (!notSticky || !settingsFlyout._sticky)) {
                                settingsFlyout._hideOrDismiss();
                            }
                        }
                    }
                },

                _hideAllFlyouts: function () {
                    _Overlay._hideFlyouts(_Global.document, true);
                    _Overlay._hideSettingsFlyouts(_Global.document, true);
                },

                _createClickEatingDivTemplate: function (divClass, hideClickEatingDivFunction) {
                    var clickEatingDiv = _Global.document.createElement("section");
                    clickEatingDiv._winHideClickEater = hideClickEatingDivFunction;
                    _ElementUtilities.addClass(clickEatingDiv, divClass);
                    _ElementUtilities._addEventListener(clickEatingDiv, "pointerup", function (event) { _Overlay._checkSameClickEatingPointerUp(event, true); }, true);
                    _ElementUtilities._addEventListener(clickEatingDiv, "pointerdown", function (event) { _Overlay._checkClickEatingPointerDown(event, true); }, true);
                    clickEatingDiv.addEventListener("click", function (event) { clickEatingDiv._winHideClickEater(event); }, true);
                    // Tell Aria that it's clickable
                    clickEatingDiv.setAttribute("role", "menuitem");
                    clickEatingDiv.setAttribute("aria-label", strings.closeOverlay);
                    // Prevent CED from removing any current selection
                    clickEatingDiv.setAttribute("unselectable", "on");
                    _Global.document.body.appendChild(clickEatingDiv);
                    return clickEatingDiv;
                },

                // Used by AppBar, and Settings Pane
                _createClickEatingDivAppBar: function () {
                    if (!_Overlay._clickEatingAppBarDiv) {
                        _Overlay._clickEatingAppBarDiv = _Overlay._createClickEatingDivTemplate(_Constants._clickEatingAppBarClass, _Overlay._handleAppBarClickEatingClick);
                    }
                },

                // Used by Flyout and Menu
                _createClickEatingDivFlyout: function () {
                    if (!_Overlay._clickEatingFlyoutDiv) {
                        _Overlay._clickEatingFlyoutDiv = _Overlay._createClickEatingDivTemplate(_Constants._clickEatingFlyoutClass, _Overlay._handleFlyoutClickEatingClick);
                    }
                },

                // All click-eaters eat "down" clicks so that we can still eat
                // the "up" click that'll come later.
                _checkClickEatingPointerDown: function (event, stopPropagation) {
                    var target = event.currentTarget;
                    if (target) {
                        try {
                            // Remember pointer id and remember right mouse
                            target._winPointerId = event.pointerId;
                            // Cache right mouse if that was what happened
                            target._winRightMouse = (event.button === 2);
                        } catch (e) { }
                    }

                    if (stopPropagation && !target._winRightMouse) {
                        event.stopPropagation();
                        event.preventDefault();
                    }
                },

                // Make sure that if we have an up we had an earlier down of the same kind
                _checkSameClickEatingPointerUp: function (event, stopPropagation) {
                    var result = false,
                        rightMouse = false,
                        target = event.currentTarget;

                    // Same pointer we were watching?
                    try {
                        if (target && target._winPointerId === event.pointerId) {
                            // Same pointer
                            result = true;
                            rightMouse = target._winRightMouse;
                            // For click-eaters, don't count right click the same because edgy will dismiss
                            if (rightMouse && stopPropagation) {
                                result = false;
                            }
                        }
                    } catch (e) { }

                    if (stopPropagation && !rightMouse) {
                        event.stopPropagation();
                        event.preventDefault();
                        target._winHideClickEater(event);
                    }

                    return result;
                },

                // If they click on a click eating div, even with a right click,
                // touch or anything, then we want to light dismiss that layer.
                _handleAppBarClickEatingClick: function (event) {
                    event.stopPropagation();
                    event.preventDefault();

                    _Overlay._hideLightDismissAppBars(null, false);
                    _Overlay._hideClickEatingDivAppBar();
                    _Overlay._hideAllFlyouts();
                },

                // If they click on a click eating div, even with a right click,
                // touch or anything, then we want to light dismiss that layer.
                _handleFlyoutClickEatingClick: function (event) {
                    event.stopPropagation();
                    event.preventDefault();

                    // Don't light dismiss AppBars because edgy will do that as needed,
                    // so flyouts only.
                    _Overlay._hideClickEatingDivFlyout();
                    _Overlay._hideFlyouts(_Global.document, true);
                },

                _checkRightClickDown: function (event) {
                    _Overlay._checkClickEatingPointerDown(event, false);
                },

                _checkRightClickUp: function (event) {
                    if (_Overlay._checkSameClickEatingPointerUp(event, false)) {
                        // It was a right click we may want to eat.
                        _Overlay._rightMouseMightEdgy = true;
                        _BaseUtils._yieldForEvents(function () { _Overlay._rightMouseMightEdgy = false; });
                    }
                },

                _showClickEatingDivAppBar: function () {
                    Scheduler.schedule(function Overlay_async_showClickEatingDivAppBar() {
                        if (_Overlay._clickEatingAppBarDiv) {
                            _Overlay._clickEatingAppBarDiv.style.display = "block";
                        }
                    }, Scheduler.Priority.high, null, "WinJS.UI._Overlay._showClickEatingDivAppBar");
                },

                _hideClickEatingDivAppBar: function () {
                    Scheduler.schedule(function Overlay_async_hideClickEatingDivAppBar() {
                        if (_Overlay._clickEatingAppBarDiv) {
                            _Overlay._clickEatingAppBarDiv.style.display = "none";
                        }
                    }, Scheduler.Priority.high, null, "WinJS.UI._Overlay._hideClickEatingDivAppBar");
                },

                _showClickEatingDivFlyout: function () {
                    Scheduler.schedule(function Overlay_async_showClickEatingDivFlyout() {
                        if (_Overlay._clickEatingFlyoutDiv) {
                            _Overlay._clickEatingFlyoutDiv.style.display = "block";
                        }
                    }, Scheduler.Priority.high, null, "WinJS.UI._Overlay._showClickEatingDivFlyout");
                },

                _hideClickEatingDivFlyout: function () {
                    Scheduler.schedule(function Overlay_async_hideClickEatingDivFlyout() {
                        if (_Overlay._clickEatingFlyoutDiv) {
                            _Overlay._clickEatingFlyoutDiv.style.display = "none";
                        }
                    }, Scheduler.Priority.high, null, "WinJS.UI._Overlay._hideClickEatingDivFlyout");
                },

                _isFlyoutVisible: function () {
                    if (!_Overlay._clickEatingFlyoutDiv) {
                        return false;
                    }
                    return (_Overlay._clickEatingFlyoutDiv.style.display === "block");
                },

                _hideIfLostFocus: function (overlay) {
                    // If we're still showing we haven't really lost focus
                    if (overlay.hidden || overlay.element.winAnimating === "showing" || overlay._sticky) {
                        return;
                    }
                    // If the active thing is within our element, we haven't lost focus
                    var active = _Global.document.activeElement;
                    if (overlay._element && overlay._element.contains(active)) {
                        return;
                    }
                    // SettingFlyouts don't dismiss if they spawned a flyout
                    if (_ElementUtilities.hasClass(overlay._element, _Constants.settingsFlyoutClass)) {
                        var settingsFlyout = overlay;
                        var flyoutControl = _Overlay._getParentControlUsingClassName(active, "win-flyout");
                        if (flyoutControl && flyoutControl._previousFocus && settingsFlyout.element.contains(flyoutControl._previousFocus)) {
                            _ElementUtilities._addEventListener(flyoutControl.element, 'focusout', function focusOut(event) {
                                // When the Flyout closes, hide the SetingsFlyout if it didn't regain focus.
                                _Overlay._hideIfLostFocus(settingsFlyout, event);
                                _ElementUtilities._removeEventListener(flyoutControl.element, 'focusout', focusOut, false);
                            }, false);
                            return;
                        }
                    }
                    // Do not hide focus if focus moved to a CED. Let the click handler on the CED take care of hiding us.
                    if (active &&
                            (_ElementUtilities.hasClass(active, _Constants._clickEatingFlyoutClass) ||
                             _ElementUtilities.hasClass(active, _Constants._clickEatingAppBarClass))) {
                        return;
                    }

                    overlay._hideOrDismiss();
                },

                // Want to hide flyouts on blur.
                // We get blur if we click off the window, including to an iframe within our window.
                // Both blurs call this function, but fortunately document.hasFocus is true if either
                // the document window or our iframe window has focus.
                _checkBlur: function () {
                    if (!_Global.document.hasFocus()) {
                        // The document doesn't have focus, so they clicked off the app, so light dismiss.
                        _Overlay._hideAllFlyouts();
                        _Overlay._hideLightDismissAppBars(null, false);
                    } else {
                        if ((_Overlay._clickEatingFlyoutDiv &&
                             _Overlay._clickEatingFlyoutDiv.style.display === "block") ||
                            (_Overlay._clickEatingAppBarDiv &&
                             _Overlay._clickEatingAppBarDiv.style.display === "block")) {
                            // We were trying to unfocus the window, but document still has focus,
                            // so make sure the iframe that took the focus will check for blur next time.
                            // We don't have to do this if the click eating div is hidden because then
                            // there would be no flyout or appbar needing light dismiss.
                            var active = _Global.document.activeElement;
                            if (active && active.tagName === "IFRAME" && !active.msLightDismissBlur) {
                                // - This will go away when the IFRAME goes away, and we only create one.
                                // - This only works in IE because other browsers don't fire focus events on iframe elements.
                                // - Can't use WinJS.Utilities._addEventListener's focusout because it doesn't fire when an
                                //   iframe loses focus due to changing windows.
                                active.addEventListener("blur", _Overlay._checkBlur, false);
                                active.msLightDismissBlur = true;
                            }
                        }
                    }
                },

                // Try to set us as active
                _trySetActive: function (element) {
                    if (!element || !_Global.document.body || !_Global.document.body.contains(element)) {
                        return false;
                    }
                    if (!_ElementUtilities._setActive(element)) {
                        return false;
                    }
                    return (element === _Global.document.activeElement);
                },

                // Try to select the text so keyboard can be used.
                _trySelect: function (element) {
                    try {
                        if (element && element.select) {
                            element.select();
                        }
                    } catch (e) { }
                },

                // Prevent the document.activeElement from showing focus
                _addHideFocusClass: function (element) {
                    if (element) {
                        _ElementUtilities.addClass(element, _Constants.hideFocusClass);
                        _ElementUtilities._addEventListener(element, "focusout", _Overlay._removeHideFocusClass, false);
                    }
                },

                // Allow the event.target (element that is losing focus) to show focus next time it gains focus
                _removeHideFocusClass: function (event) {
                    // Make sure we really lost focus and was not just an App switch
                    var target = event.target;
                    if (target && target !== _Global.document.activeElement) {
                        _ElementUtilities.removeClass(target, _Constants.hideFocusClass);
                        _ElementUtilities._removeEventListener(event.target, "focusout", _Overlay._removeHideFocusClass, false);
                    }
                },

                _sizeOfDocument: function () {
                    return {
                        width: _Global.document.documentElement.offsetWidth,
                        height: _Global.document.documentElement.offsetHeight,
                    };
                },

                _getParentControlUsingClassName: function (element, className) {
                    while (element && element !== _Global.document.body) {
                        if (_ElementUtilities.hasClass(element, className)) {
                            return element.winControl;
                        }
                        element = element.parentNode;
                    }
                    return null;
                },

                // Hide all light dismiss AppBars if what has focus is not part of a AppBar or flyout.
                _hideIfAllAppBarsLostFocus: function _hideIfAllAppBarsLostFocus() {
                    if (!_Overlay._isAppBarOrChild(_Global.document.activeElement)) {
                        _Overlay._hideLightDismissAppBars(null, false);
                        // Ensure that sticky appbars clear cached focus after light dismiss are dismissed, which moved focus.
                        _Overlay._ElementWithFocusPreviousToAppBar = null;
                    }
                },

                _hideLightDismissAppBars: function (event, keyboardInvoked) {
                    var elements = _Global.document.querySelectorAll("." + _Constants.appBarClass);
                    var len = elements.length;
                    var AppBars = [];
                    for (var i = 0; i < len; i++) {
                        var AppBar = elements[i].winControl;
                        if (AppBar && !AppBar.sticky && !AppBar.hidden) {
                            AppBars.push(AppBar);
                        }
                    }

                    _Overlay._hideAllBars(AppBars, keyboardInvoked);
                },

                // Show/Hide all bars
                _hideAllBars: function _Overlay_hideAllBars(bars, keyboardInvoked) {
                    var allBarsAnimationPromises = bars.map(function (bar) {
                        bar._keyboardInvoked = keyboardInvoked;
                        bar.hide();
                        return bar._animationPromise;
                    });
                    return Promise.join(allBarsAnimationPromises);
                },

                _showAllBars: function _Overlay_showAllBars(bars, keyboardInvoked) {
                    var allBarsAnimationPromises = bars.map(function (bar) {
                        bar._keyboardInvoked = keyboardInvoked;
                        bar._doNotFocus = false;
                        bar._show();
                        return bar._animationPromise;
                    });
                    return Promise.join(allBarsAnimationPromises);
                },

                // Returns appbar element (or CED/sentinal) if the element or what had focus before the element (if a Flyout) is either:
                //   1) an AppBar,
                //   2) OR in the subtree of an AppBar,
                //   3) OR an AppBar click eating div.
                // Returns null otherwise.
                _isAppBarOrChild: function (element) {
                    // If it's null, we can't do this
                    if (!element) {
                        return null;
                    }

                    // Intrinsic components of the AppBar count as the AppBar
                    if (_ElementUtilities.hasClass(element, _Constants._clickEatingAppBarClass) ||
                        _ElementUtilities.hasClass(element, _Constants._clickEatingFlyoutClass) ||
                        _ElementUtilities.hasClass(element, _Constants.firstDivClass) ||
                        _ElementUtilities.hasClass(element, _Constants.finalDivClass) ||
                        _ElementUtilities.hasClass(element, _Constants.invokeButtonClass)) {
                        return element;
                    }

                    while (element && element !== _Global.document) {
                        if (_ElementUtilities.hasClass(element, _Constants.appBarClass)) {
                            return element;
                        }
                        if (_ElementUtilities.hasClass(element, "win-flyout")
                         && element !== element.winControl._previousFocus) {
                            var flyoutControl = element.winControl;
                            // If _previousFocus was in a light dismissable AppBar, then this Flyout is considered of an extension of it and that AppBar should not hide.
                            // Hook up a 'focusout' listener to this Flyout element to make sure that light dismiss AppBars hide if focus moves anywhere other than back to an AppBar.
                            var appBarElement = _Overlay._isAppBarOrChild(flyoutControl._previousFocus);
                            if (appBarElement) {
                                _ElementUtilities._addEventListener(flyoutControl.element, 'focusout', function focusOut() {
                                    // Hides any shown AppBars if the new activeElement is not in an AppBar.
                                    _Overlay._hideIfAllAppBarsLostFocus();
                                    _ElementUtilities._removeEventListener(flyoutControl.element, 'focusout', focusOut, false);
                                }, false);
                            }
                            return appBarElement;
                        }

                        element = element.parentNode;
                    }

                    return null;
                },

                // WWA Soft Keyboard offsets
                _keyboardInfo: {
                    // Determine if the keyboard is visible or not.
                    get _visible() {

                        try {
                            return (
                                _WinRT.Windows.UI.ViewManagement.InputPane &&
                                _WinRT.Windows.UI.ViewManagement.InputPane.getForCurrentView().occludedRect.height > 0
                            );
                        } catch (e) {
                            return false;
                        }

                    },

                    // See if we have to reserve extra space for the IHM
                    get _extraOccluded() {
                        var occluded;
                        if (_WinRT.Windows.UI.ViewManagement.InputPane) {
                            try {
                                occluded = _WinRT.Windows.UI.ViewManagement.InputPane.getForCurrentView().occludedRect.height;
                            } catch (e) {
                            }
                        }

                        // Nothing occluded if not visible.
                        if (occluded && !_Overlay._keyboardInfo._isResized) {
                            // View hasn't been resized, need to return occluded height.
                            return occluded;
                        }

                        // View already has space for keyboard or there's no keyboard
                        return 0;

                    },

                    // See if the view has been resized to fit a keyboard
                    get _isResized() {
                        // Compare ratios.  Very different includes IHM space.
                        var heightRatio = _Global.document.documentElement.clientHeight / _Global.innerHeight,
                            widthRatio = _Global.document.documentElement.clientWidth / _Global.innerWidth;

                        // If they're nearly identical, then the view hasn't been resized for the IHM
                        // Only check one bound because we know the IHM will make it shorter, not skinnier.
                        return (widthRatio / heightRatio < 0.99);

                    },

                    // Get the bottom of our visible area.
                    get _visibleDocBottom() {
                        return _Overlay._keyboardInfo._visibleDocTop + _Overlay._keyboardInfo._visibleDocHeight;

                    },

                    // Get the height of the visible document, e.g. the height of the visual viewport minus any IHM occlusion.
                    get _visibleDocHeight() {
                        return _Overlay._keyboardInfo._visualViewportHeight - _Overlay._keyboardInfo._extraOccluded;

                    },

                    // Get total length of the IHM showPanel animation
                    get _animationShowLength() {
                        if (_WinRT.Windows.UI.Core.AnimationMetrics) {
                            var a = _WinRT.Windows.UI.Core.AnimationMetrics,
                            animationDescription = new a.AnimationDescription(a.AnimationEffect.showPanel, a.AnimationEffectTarget.primary);
                            var animations = animationDescription.animations;
                            var max = 0;
                            for (var i = 0; i < animations.size; i++) {
                                var animation = animations[i];
                                max = Math.max(max, animation.delay + animation.duration);
                            }
                            return max;
                        } else {
                            return 0;
                        }
                    },
                },

                _ElementWithFocusPreviousToAppBar: null,

                // for tests
                _clickEatingAppBarClass: _Constants._clickEatingAppBarClass,
                _clickEatingFlyoutClass: _Constants._clickEatingFlyoutClass,

                // Padding for IHM timer to allow for first scroll event
                _scrollTimeout: 150,

                // Events
                beforeShow: BEFORESHOW,
                beforeHide: BEFOREHIDE,
                afterShow: AFTERSHOW,
                afterHide: AFTERHIDE,

                commonstrings: {
                    get cannotChangeCommandsWhenVisible() { return "Invalid argument: You must call hide() before changing {0} commands"; },
                    get cannotChangeHiddenProperty() { return "Unable to set hidden property while parent {0} is visible."; }
                }
            });

            // Mixin for WWA's Soft Keyboard offsets when -ms-device-fixed CSS positioning is supported, or for general _Overlay positioning whenever we are in a web browser outside of WWA.
            // If we are in an instance of WWA, all _Overlay elements will use -ms-device-fixed positioning which fixes them to the visual viewport directly.
            var _keyboardInfo_Mixin = {

                // Get the top offset of our visible area, aka the top of the visual viewport.
                // This is always 0 when _Overlay elements use -ms-device-fixed positioning.
                _visibleDocTop: function _visibleDocTop() {
                    return 0;
                },

                // Get the bottom offset of the visual viewport, plus any IHM occlusion.
                _visibleDocBottomOffset: function _visibleDocBottomOffset() {
                    // For -ms-device-fixed positioned elements, the bottom is just 0 when there's no IHM.
                    // When the IHM appears, the text input that invoked it may be in a position on the page that is occluded by the IHM.
                    // In that instance, the default browser behavior is to resize the visual viewport and scroll the input back into view.
                    // However, if the viewport resize is prevented by an IHM event listener, the keyboard will still occlude
                    // -ms-device-fixed elements, so we adjust the bottom offset of the appbar by the height of the occluded rect of the IHM.
                    return (_Overlay._keyboardInfo._isResized) ? 0 : _Overlay._keyboardInfo._extraOccluded;
                },

                // Get the visual viewport height. window.innerHeight doesn't return floating point values which are present with high DPI.
                _visualViewportHeight: function _visualViewportHeight() {
                    var boundingRect = _Overlay._keyboardInfo._visualViewportSpace;
                    return boundingRect.bottom - boundingRect.top;
                },

                // Get the visual viewport width. window.innerWidth doesn't return floating point values which are present with high DPI.
                _visualViewportWidth: function _visualViewportWidth() {
                    var boundingRect = _Overlay._keyboardInfo._visualViewportSpace;
                    return boundingRect.right - boundingRect.left;
                },

                _visualViewportSpace: function _visualViewportSpace() {
                    var visualViewportSpace = _Global.document.body.querySelector("." + _Constants._visualViewportClass);
                    if (!visualViewportSpace) {
                        visualViewportSpace = _Global.document.createElement("DIV");
                        visualViewportSpace.className = _Constants._visualViewportClass;
                        _Global.document.body.appendChild(visualViewportSpace);
                    }
                    return visualViewportSpace.getBoundingClientRect();
                },
            };

            // Mixin for WWA's Soft Keyboard offsets in IE10 mode, where -ms-device-fixed positioning is not available.
            // In that instance, all _Overlay elements fall back to using CSS fixed positioning.
            // This is for backwards compatibility with Apache Cordova Apps targeting WWA since they target IE10.
            // This is essentially the original logic for WWA _Overlay / Soft Keyboard interactions we used when windows 8 first launched.
            var _keyboardInfo_Windows8WWA_Mixin = {
                // Get the top of our visible area in terms of its absolute distance from the top of document.documentElement.
                // Normalizes any offsets which have have occured between the visual viewport and the layout viewport due to resizing the viewport to fit the IHM and/or optical zoom.
                _visibleDocTop: function _visibleDocTop_Windows8WWA() {
                    return _Global.window.pageYOffset - _Global.document.documentElement.scrollTop;
                },

                // Get the bottom offset of the visual viewport from the bottom of the layout viewport, plus any IHM occlusion.
                _visibleDocBottomOffset: function _visibleDocBottomOffset_Windows8WWA() {
                    return _Global.document.documentElement.clientHeight - _Overlay._keyboardInfo._visibleDocBottom;
                },

                _visualViewportHeight: function _visualViewportHeight_Windows8WWA() {
                    return _Global.window.innerHeight;
                },

                _visualViewportWidth: function _visualViewportWidth_Windows8WWA() {
                    return _Global.window.innerWidth;
                },
            };

            _Base.Class.mix(_Overlay, _Control.DOMEventMixin);

            // Feature detect for -ms-device-fixed positioning and fill out the
            // remainder of our WWA Soft KeyBoard handling logic with mixins.
            var visualViewportSpace = _Global.document.createElement("DIV");
            visualViewportSpace.className = _Constants._visualViewportClass;
            _Global.document.body.appendChild(visualViewportSpace);

            var propertiesMixin,
                hasDeviceFixed = _Global.getComputedStyle(visualViewportSpace).position === "-ms-device-fixed";
            if (!hasDeviceFixed && _WinRT.Windows.UI.ViewManagement.InputPane) {
                // If we are in WWA with IE 10 mode, use special keyboard handling knowledge for IE10 IHM.
                propertiesMixin = _keyboardInfo_Windows8WWA_Mixin;
                _Global.document.body.removeChild(visualViewportSpace);
            } else {
                // If we are in WWA on IE 11 or outside of WWA on any web browser use general positioning logic.
                propertiesMixin = _keyboardInfo_Mixin;
            }

            for (var propertyName in propertiesMixin) {
                Object.defineProperty(_Overlay._keyboardInfo, propertyName, {
                    get: propertiesMixin[propertyName],
                });
            }

            return _Overlay;
        })
    });

});


// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
/// <dictionary>appbar,Flyout,Flyouts,Statics</dictionary>
define('WinJS/Controls/Flyout',[
    'exports',
    '../Core/_Global',
    '../Core/_Base',
    '../Core/_BaseUtils',
    '../Core/_ErrorFromName',
    '../Core/_Resources',
    '../Core/_WriteProfilerMark',
    '../Animations',
    '../Utilities/_Dispose',
    '../Utilities/_ElementUtilities',
    '../Utilities/_Hoverable',
    './AppBar/_Constants',
    './Flyout/_Overlay',
    'require-style!less/desktop/controls',
    'require-style!less/phone/controls'
    ], function flyoutInit(exports, _Global, _Base, _BaseUtils, _ErrorFromName, _Resources, _WriteProfilerMark, Animations, _Dispose, _ElementUtilities, _Hoverable, _Constants, _Overlay) {
    "use strict";

    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {
        /// <field>
        /// <summary locid="WinJS.UI.Flyout">
        /// Displays lightweight UI that is either informational, or requires user interaction.
        /// Unlike a dialog, a Flyout can be light dismissed by clicking or tapping off of it.
        /// </summary>
        /// <compatibleWith platform="Windows" minVersion="8.0"/>
        /// </field>
        /// <name locid="WinJS.UI.Flyout_name">Flyout</name>
        /// <icon src="ui_winjs.ui.flyout.12x12.png" width="12" height="12" />
        /// <icon src="ui_winjs.ui.flyout.16x16.png" width="16" height="16" />
        /// <htmlSnippet supportsContent="true"><![CDATA[<div data-win-control="WinJS.UI.Flyout"></div>]]></htmlSnippet>
        /// <event name="beforeshow" locid="WinJS.UI.Flyout_e:beforeshow">Raised just before showing a flyout.</event>
        /// <event name="aftershow" locid="WinJS.UI.Flyout_e:aftershow">Raised immediately after a flyout is fully shown.</event>
        /// <event name="beforehide" locid="WinJS.UI.Flyout_e:beforehide">Raised just before hiding a flyout.</event>
        /// <event name="afterhide" locid="WinJS.UI.Flyout_e:afterhide">Raised immediately after a flyout is fully hidden.</event>
        /// <part name="flyout" class="win-flyout" locid="WinJS.UI.Flyout_part:flyout">The Flyout control itself.</part>
        /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
        /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
        /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
        Flyout: _Base.Namespace._lazy(function () {
            var Key = _ElementUtilities.Key;

            function getDimension(element, property) {
                return parseFloat(element, _Global.getComputedStyle(element, null)[property]);
            }

            var strings = {
                get ariaLabel() { return _Resources._getWinJSString("ui/flyoutAriaLabel").value; },
                get noAnchor() { return "Invalid argument: Showing flyout requires a DOM element as its parameter."; },
                get badPlacement() { return "Invalid argument: Flyout placement should be 'top' (default), 'bottom', 'left', 'right', or 'auto'."; },
                get badAlignment() { return "Invalid argument: Flyout alignment should be 'center' (default), 'left', or 'right'."; }
            };

            var Flyout = _Base.Class.derive(_Overlay._Overlay, function Flyout_ctor(element, options) {
                /// <signature helpKeyword="WinJS.UI.Flyout.Flyout">
                /// <summary locid="WinJS.UI.Flyout.constructor">
                /// Creates a new Flyout control.
                /// </summary>
                /// <param name="element" type="HTMLElement" domElement="true" locid="WinJS.UI.Flyout.constructor_p:element">
                /// The DOM element that hosts the control.
                /// </param>
                /// <param name="options" type="Object" domElement="false" locid="WinJS.UI.Flyout.constructor_p:options">
                /// The set of properties and values to apply to the new Flyout.
                /// </param>
                /// <returns type="WinJS.UI.Flyout" locid="WinJS.UI.Flyout.constructor_returnValue">The new Flyout control.</returns>
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </signature>

                // Simplify checking later
                options = options || {};

                // Make sure there's an input element
                this._element = element || _Global.document.createElement("div");
                this._id = this._element.id || _ElementUtilities._uniqueID(this._element);
                this._writeProfilerMark("constructor,StartTM");

                this._baseFlyoutConstructor(this._element, options);

                var _elms = this._element.getElementsByTagName("*");
                var firstDiv = this._addFirstDiv();
                firstDiv.tabIndex = _ElementUtilities._getLowestTabIndexInList(_elms);
                var finalDiv = this._addFinalDiv();
                finalDiv.tabIndex = _ElementUtilities._getHighestTabIndexInList(_elms);

                // Handle "esc" & "tab" key presses
                this._element.addEventListener("keydown", this._handleKeyDown, true);

                this._writeProfilerMark("constructor,StopTM");
                return this;
            }, {
                _lastMaxHeight: null,

                _baseFlyoutConstructor: function Flyout_baseFlyoutContstructor(element, options) {
                    // Flyout constructor

                    // We have some options with defaults
                    this._placement = "auto";
                    this._alignment = "center";

                    // Call the base overlay constructor helper
                    this._baseOverlayConstructor(element, options);

                    // Make a click eating div
                    _Overlay._Overlay._createClickEatingDivFlyout();

                    // Start flyouts hidden
                    this._element.style.visibilty = "hidden";
                    this._element.style.display = "none";

                    // Attach our css class
                    _ElementUtilities.addClass(this._element, _Constants.flyoutClass);
                    _ElementUtilities.addClass(this._element, _Constants.flyoutLightClass);

                    // Make sure we have an ARIA role
                    var role = this._element.getAttribute("role");
                    if (role === null || role === "" || role === undefined) {
                        if (_ElementUtilities.hasClass(this._element, _Constants.menuClass)) {
                            this._element.setAttribute("role", "menu");
                        } else {
                            this._element.setAttribute("role", "dialog");
                        }
                    }
                    var label = this._element.getAttribute("aria-label");
                    if (label === null || label === "" || label === undefined) {
                        this._element.setAttribute("aria-label", strings.ariaLabel);
                    }

                    // Base animation is popIn, but our flyout has different arguments
                    this._currentAnimateIn = this._flyoutAnimateIn;
                    this._currentAnimateOut = this._flyoutAnimateOut;

                    // Make sure _Overlay event handlers are hooked up
                    this._addOverlayEventHandlers(true);
                },

                /// <field type="String" locid="WinJS.UI.Flyout.anchor" helpKeyword="WinJS.UI.Flyout.anchor">
                /// Gets or sets the Flyout control's anchor. The anchor element is the HTML element which the Flyout originates from and is positioned relative to.
                /// (This setting can be overridden when you call the show method.)
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                anchor: {
                    get: function () {
                        return this._anchor;
                    },
                    set: function (value) {
                        this._anchor = value;
                    }
                },

                /// <field type="String" defaultValue="auto" oamOptionsDatatype="WinJS.UI.Flyout.placement" locid="WinJS.UI.Flyout.placement" helpKeyword="WinJS.UI.Flyout.placement">
                /// Gets or sets the default placement of this Flyout. (This setting can be overridden when you call the show method.)
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                placement: {
                    get: function () {
                        return this._placement;
                    },
                    set: function (value) {
                        if (value !== "top" && value !== "bottom" && value !== "left" && value !== "right" && value !== "auto") {
                            // Not a legal placement value
                            throw new _ErrorFromName("WinJS.UI.Flyout.BadPlacement", strings.badPlacement);
                        }
                        this._placement = value;
                    }
                },

                /// <field type="String" defaultValue="center" oamOptionsDatatype="WinJS.UI.Flyout.alignment" locid="WinJS.UI.Flyout.alignment" helpKeyword="WinJS.UI.Flyout.alignment">
                /// Gets or sets the default alignment for this Flyout. (This setting can be overridden when you call the show method.)
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                alignment: {
                    get: function () {
                        return this._alignment;
                    },
                    set: function (value) {
                        if (value !== "right" && value !== "left" && value !== "center") {
                            // Not a legal alignment value
                            throw new _ErrorFromName("WinJS.UI.Flyout.BadAlignment", strings.badAlignment);
                        }
                        this._alignment = value;
                    }
                },

                _dispose: function Flyout_dispose() {
                    _Dispose.disposeSubTree(this.element);
                    this._hide();
                    this.anchor = null;
                },

                show: function (anchor, placement, alignment) {
                    /// <signature helpKeyword="WinJS.UI.Flyout.show">
                    /// <summary locid="WinJS.UI.Flyout.show">
                    /// Shows the Flyout, if hidden, regardless of other states.
                    /// </summary>
                    /// <param name="anchor" type="HTMLElement" domElement="true" locid="WinJS.UI.Flyout.show_p:anchor">
                    /// The DOM element, or ID of a DOM element to anchor the Flyout, overriding the anchor property for this time only.
                    /// </param>
                    /// <param name="placement" type="Object" domElement="false" locid="WinJS.UI.Flyout.show_p:placement">
                    /// The placement of the Flyout to the anchor: 'auto' (default), 'top', 'bottom', 'left', or 'right'.  This parameter overrides the placement property for this show only.
                    /// </param>
                    /// <param name="alignment" type="Object" domElement="false" locid="WinJS.UI.Flyout.show:alignment">
                    /// For 'top' or 'bottom' placement, the alignment of the Flyout to the anchor's edge: 'center' (default), 'left', or 'right'.
                    /// This parameter overrides the alignment property for this show only.
                    /// </param>
                    /// <compatibleWith platform="Windows" minVersion="8.0"/>
                    /// </signature>
                    this._writeProfilerMark("show,StartTM"); // The corresponding "stop" profiler mark is handled in _Overlay._baseEndShow().
                    // Just call private version to make appbar flags happy
                    this._show(anchor, placement, alignment);
                },

                _show: function Flyout_show(anchor, placement, alignment) {
                    this._baseFlyoutShow(anchor, placement, alignment);
                },

                hide: function () {
                    /// <signature helpKeyword="WinJS.UI.Flyout.hide">
                    /// <summary locid="WinJS.UI.Flyout.hide">
                    /// Hides the Flyout, if visible, regardless of other states.
                    /// </summary>
                    /// <compatibleWith platform="Windows" minVersion="8.0"/>
                    /// </signature>
                    // Just wrap the private one, turning off keyboard invoked flag
                    this._writeProfilerMark("hide,StartTM"); // The corresponding "stop" profiler mark is handled in _Overlay._baseEndHide().
                    this._keyboardInvoked = false;
                    this._hide();
                },

                _hide: function Flyout_hide() {
                    if (this._baseHide()) {
                        // Return focus if this or the flyout CED has focus
                        var active = _Global.document.activeElement;
                        if (this._previousFocus
                           && active
                           && (this._element.contains(active)
                               || _ElementUtilities.hasClass(active, _Overlay._Overlay._clickEatingFlyoutClass))
                           && this._previousFocus.focus !== undefined) {

                            // _isAppBarOrChild may return a CED or sentinal
                            var appBar = _Overlay._Overlay._isAppBarOrChild(this._previousFocus);
                            if (!appBar || (appBar.winControl && !appBar.winControl.hidden && !appBar.winAnimating)) {
                                // Don't move focus back to a appBar that is hidden
                                // We cannot rely on element.style.visibility because it will be visible while animating
                                var role = this._previousFocus.getAttribute("role");
                                var fHideRole = _Overlay._Overlay._keyboardInfo._visible && !this._keyboardWasUp;
                                if (fHideRole) {
                                    // Convince IHM to dismiss because it only came up after the flyout was up.
                                    // Change aria role and back to get IHM to dismiss.
                                    this._previousFocus.setAttribute("role", "");
                                }

                                if (this._keyboardInvoked) {
                                    this._previousFocus.focus();
                                } else {
                                    _Overlay._Overlay._trySetActive(this._previousFocus);
                                }
                                active = _Global.document.activeElement;

                                if (fHideRole) {
                                    // Restore the role so that css is applied correctly
                                    var previousFocus = this._previousFocus;
                                    if (previousFocus) {
                                        _BaseUtils._yieldForDomModification(function () {
                                            previousFocus.setAttribute("role", role);
                                        });
                                    }
                                }
                            }

                            // If the anchor gained focus we want to hide the focus in the non-keyboarding scenario
                            if (!this._keyboardInvoked && (this._previousFocus === active) && appBar && active) {
                                _Overlay._Overlay._addHideFocusClass(active);
                            }
                        }

                        this._previousFocus = null;

                        // Need click-eating div to be hidden if there are no other visible flyouts
                        if (!this._isThereVisibleFlyout()) {
                            _Overlay._Overlay._hideClickEatingDivFlyout();
                        }
                    }
                },

                _baseFlyoutShow: function Flyout_baseFlyoutShow(anchor, placement, alignment) {
                    // Don't do anything if disabled
                    if (this.disabled) {
                        return;
                    }
                    // Pick up defaults
                    if (!anchor) {
                        anchor = this._anchor;
                    }
                    if (!placement) {
                        placement = this._placement;
                    }
                    if (!alignment) {
                        alignment = this._alignment;
                    }

                    // Dereference the anchor if necessary
                    if (typeof anchor === "string") {
                        anchor = _Global.document.getElementById(anchor);
                    } else if (anchor && anchor.element) {
                        anchor = anchor.element;
                    }

                    // We expect an anchor
                    if (!anchor) {
                        // If we have _nextLeft, etc., then we were continuing an old animation, so that's OK
                        if (!this._retryLast) {
                            throw new _ErrorFromName("WinJS.UI.Flyout.NoAnchor", strings.noAnchor);
                        }
                        // Last call was incomplete, so use the previous _current values.
                        this._retryLast = null;
                    } else {
                        // Remember the anchor so that if we lose focus we can go back
                        this._currentAnchor = anchor;
                        // Remember current values
                        this._currentPlacement = placement;
                        this._currentAlignment = alignment;
                    }

                    // Need click-eating div to be visible, no matter what
                    if (!this._sticky) {
                        _Overlay._Overlay._showClickEatingDivFlyout();
                    }

                    // If we're animating (eg baseShow is going to fail), then don't mess up our current state.
                    // Queue us up to wait for current animation to finish first.
                    if (this._element.winAnimating) {
                        this._doNext = "show";
                        this._retryLast = true;
                        return;
                    }

                    // We call our base _baseShow to handle the actual animation
                    if (this._baseShow()) {
                        // (_baseShow shouldn't ever fail because we tested winAnimating above).
                        if (!_ElementUtilities.hasClass(this.element, "win-menu")) {
                            // Verify that the firstDiv is in the correct location.
                            // Move it to the correct location or add it if not.
                            var _elms = this._element.getElementsByTagName("*");
                            var firstDiv = this.element.querySelectorAll(".win-first");
                            if (this.element.children.length && !_ElementUtilities.hasClass(this.element.children[0], _Constants.firstDivClass)) {
                                if (firstDiv && firstDiv.length > 0) {
                                    firstDiv.item(0).parentNode.removeChild(firstDiv.item(0));
                                }

                                firstDiv = this._addFirstDiv();
                            }
                            firstDiv.tabIndex = _ElementUtilities._getLowestTabIndexInList(_elms);

                            // Verify that the finalDiv is in the correct location.
                            // Move it to the correct location or add it if not.
                            var finalDiv = this.element.querySelectorAll(".win-final");
                            if (!_ElementUtilities.hasClass(this.element.children[this.element.children.length - 1], _Constants.finalDivClass)) {
                                if (finalDiv && finalDiv.length > 0) {
                                    finalDiv.item(0).parentNode.removeChild(finalDiv.item(0));
                                }

                                finalDiv = this._addFinalDiv();
                            }
                            finalDiv.tabIndex = _ElementUtilities._getHighestTabIndexInList(_elms);
                        }

                        // Hide all other flyouts
                        this._hideAllOtherFlyouts(this);

                        // Store what had focus before showing the Flyout.
                        // This must happen after we hide all other flyouts so that we store the correct element.
                        this._previousFocus = _Global.document.activeElement;
                    }
                },

                _endShow: function Flyout_endShow() {
                    // Remember if the IHM was up since we may need to hide it when the flyout hides.
                    // This check needs to happen after the IHM has a chance to hide itself after we force hide
                    // all other visible Flyouts.
                    this._keyboardWasUp = _Overlay._Overlay._keyboardInfo._visible;

                    if (!_ElementUtilities.hasClass(this.element, _Constants.menuClass)) {
                        // Put focus on the first child in the Flyout
                        this._focusOnFirstFocusableElementOrThis();

                        // Prevent what is gaining focus from showing that it has focus
                        _Overlay._Overlay._addHideFocusClass(_Global.document.activeElement);
                    } else {
                        // Make sure the menu has focus, but don't show a focus rect
                        _Overlay._Overlay._trySetActive(this._element);
                    }
                },

                // Find our new flyout position.
                _findPosition: function Flyout_findPosition() {
                    this._nextHeight = null;
                    this._keyboardMovedUs = false;
                    this._hasScrolls = false;
                    this._keyboardSquishedUs = 0;

                    // Make sure menu toggles behave
                    if (this._checkToggle) {
                        this._checkToggle();
                    }

                    // Update margins for this alignment and remove old scrolling
                    this._updateAdjustments(this._currentAlignment);

                    // Set up the new position, and prep the offset for showPopup
                    this._getTopLeft();
                    // Panning top offset is calculated top
                    this._scrollTop = this._nextTop - _Overlay._Overlay._keyboardInfo._visibleDocTop;

                    // Adjust position
                    if (this._nextTop < 0) {
                        // Need to attach to bottom
                        this._element.style.bottom = "0px";
                        this._element.style.top = "auto";
                    } else {
                        // Normal, attach to top
                        this._element.style.top = this._nextTop + "px";
                        this._element.style.bottom = "auto";
                    }
                    if (this._nextLeft < 0) {
                        // Overran right, attach to right
                        this._element.style.right = "0px";
                        this._element.style.left = "auto";
                    } else {
                        // Normal, attach to left
                        this._element.style.left = this._nextLeft + "px";
                        this._element.style.right = "auto";
                    }

                    // Adjust height/scrollbar
                    if (this._nextHeight !== null) {
                        _ElementUtilities.addClass(this._element, _Constants.scrollsClass);
                        this._lastMaxHeight = this._element.style.maxHeight;
                        this._element.style.maxHeight = this._nextHeight + "px";
                        this._nextBottom = this._nextTop + this._nextHeight;
                        this._hasScrolls = true;
                    }

                    // May need to adjust if the IHM is showing.
                    if (_Overlay._Overlay._keyboardInfo._visible) {
                        // Use keyboard logic
                        this._checkKeyboardFit();

                        if (this._keyboardMovedUs) {
                            this._adjustForKeyboard();
                        }
                    }
                },

                // This determines our positioning.  We have 5 modes, the 1st four are explicit, the last is automatic:
                // * top - position explicitly on the top of the anchor, shrinking and adding scrollbar as needed.
                // * bottom - position explicitly below the anchor, shrinking and adding scrollbar as needed.
                // * left - position left of the anchor, shrinking and adding a vertical scrollbar as needed.
                // * right - position right of the anchor, shrinking and adding a vertical scroolbar as needed.
                // * auto - Automatic placement.
                // Auto tests the height of the anchor and the flyout.  For consistency in orientation, we imagine
                // that the anchor is placed in the vertical center of the display.  If the flyout would fit above
                // that centered anchor, then we will place the flyout vertically in relation to the anchor, otherwise
                // placement will be horizontal.
                // Vertical auto placement will be positioned on top of the anchor if room, otherwise below the anchor.
                //   - this is because touch users would be more likely to obscure flyouts below the anchor.
                // Horizontal auto placement will be positioned to the left of the anchor if room, otherwise to the right.
                //   - this is because right handed users would be more likely to obscure a flyout on the right of the anchor.
                // Auto placement will add a vertical scrollbar if necessary.
                _getTopLeft: function Flyout_getTopLeft() {
                    var anchorRawRectangle = this._currentAnchor.getBoundingClientRect(),
                        flyout = {},
                        anchor = {};

                    // Adjust for the anchor's margins.
                    anchor.top = anchorRawRectangle.top;
                    anchor.bottom = anchorRawRectangle.bottom;
                    anchor.left = anchorRawRectangle.left;
                    anchor.right = anchorRawRectangle.right;
                    anchor.height = anchor.bottom - anchor.top;
                    anchor.width = anchor.right - anchor.left;

                    // Get our flyout and margins, note that getDimension calls
                    // window.getComputedStyle, which ensures layout is updated.
                    flyout.marginTop = getDimension(this._element, "marginTop");
                    flyout.marginBottom = getDimension(this._element, "marginBottom");
                    flyout.marginLeft = getDimension(this._element, "marginLeft");
                    flyout.marginRight = getDimension(this._element, "marginRight");
                    flyout.width = _ElementUtilities.getTotalWidth(this._element);
                    flyout.height = _ElementUtilities.getTotalHeight(this._element);
                    flyout.innerWidth = _ElementUtilities.getContentWidth(this._element);
                    flyout.innerHeight = _ElementUtilities.getContentHeight(this._element);
                    this._nextMarginPadding = (flyout.height - flyout.innerHeight);

                    // Check fit for requested this._currentPlacement, doing fallback if necessary
                    switch (this._currentPlacement) {
                        case "top":
                            if (!this._fitTop(anchor, flyout)) {
                                // Didn't fit, needs scrollbar
                                this._nextTop = _Overlay._Overlay._keyboardInfo._visibleDocTop;
                                this._nextHeight = anchor.top - _Overlay._Overlay._keyboardInfo._visibleDocTop - this._nextMarginPadding;
                            }
                            this._centerHorizontally(anchor, flyout, this._currentAlignment);
                            break;
                        case "bottom":
                            if (!this._fitBottom(anchor, flyout)) {
                                // Didn't fit, needs scrollbar
                                this._nextTop = -1;
                                this._nextHeight = _Overlay._Overlay._keyboardInfo._visibleDocHeight - (anchor.bottom - _Overlay._Overlay._keyboardInfo._visibleDocTop) - this._nextMarginPadding;
                            }
                            this._centerHorizontally(anchor, flyout, this._currentAlignment);
                            break;
                        case "left":
                            if (!this._fitLeft(anchor, flyout)) {
                                // Didn't fit, just shove it to edge
                                this._nextLeft = 0;
                            }
                            this._centerVertically(anchor, flyout);
                            break;
                        case "right":
                            if (!this._fitRight(anchor, flyout)) {
                                // Didn't fit,just shove it to edge
                                this._nextLeft = -1;
                            }
                            this._centerVertically(anchor, flyout);
                            break;
                        case "auto":
                            // Auto, if the anchor was in the vertical center of the display would we fit above it?
                            if (this._sometimesFitsAbove(anchor, flyout)) {
                                // It will fit above or below the anchor
                                if (!this._fitTop(anchor, flyout)) {
                                    // Didn't fit above (preferred), so go below.
                                    this._fitBottom(anchor, flyout);
                                }
                                this._centerHorizontally(anchor, flyout, this._currentAlignment);
                            } else {
                                // Won't fit above or below, try a side
                                if (!this._fitLeft(anchor, flyout) &&
                                    !this._fitRight(anchor, flyout)) {
                                    // Didn't fit left or right either, is top or bottom bigger?
                                    if (this._topHasMoreRoom(anchor)) {
                                        // Top, won't fit, needs scrollbar
                                        this._nextTop = _Overlay._Overlay._keyboardInfo._visibleDocTop;
                                        this._nextHeight = anchor.top - _Overlay._Overlay._keyboardInfo._visibleDocTop - this._nextMarginPadding;
                                    } else {
                                        // Bottom, won't fit, needs scrollbar
                                        this._nextTop = -1;
                                        this._nextHeight = _Overlay._Overlay._keyboardInfo._visibleDocHeight - (anchor.bottom - _Overlay._Overlay._keyboardInfo._visibleDocTop) - this._nextMarginPadding;
                                    }
                                    this._centerHorizontally(anchor, flyout, this._currentAlignment);
                                } else {
                                    this._centerVertically(anchor, flyout);
                                }
                            }
                            break;
                        default:
                            // Not a legal this._currentPlacement value
                            throw new _ErrorFromName("WinJS.UI.Flyout.BadPlacement", strings.badPlacement);
                    }

                    // Remember "bottom" in case we need to consider keyboard later, only tested for top-pinned bars
                    this._nextBottom = this._nextTop + flyout.height;
                },

                // If the anchor is centered vertically, would the flyout fit above it?
                _sometimesFitsAbove: function Flyout_sometimesFitsAbove(anchor, flyout) {
                    return ((_Overlay._Overlay._keyboardInfo._visibleDocHeight - anchor.height) / 2) >= flyout.height;
                },

                _topHasMoreRoom: function Flyout_topHasMoreRoom(anchor) {
                    return anchor.top > _Overlay._Overlay._keyboardInfo._visibleDocHeight - anchor.bottom;
                },

                // See if we can fit in various places, fitting in the main view,
                // ignoring viewport changes, like for the IHM.
                _fitTop: function Flyout_fitTop(anchor, flyout) {
                    this._nextTop = anchor.top - flyout.height;
                    this._nextAnimOffset = { top: "50px", left: "0px", keyframe: "WinJS-showFlyoutTop" };
                    return (this._nextTop >= _Overlay._Overlay._keyboardInfo._visibleDocTop &&
                            this._nextTop + flyout.height <= _Overlay._Overlay._keyboardInfo._visibleDocBottom);
                },

                _fitBottom: function Flyout_fitBottom(anchor, flyout) {
                    this._nextTop = anchor.bottom;
                    this._nextAnimOffset = { top: "-50px", left: "0px", keyframe: "WinJS-showFlyoutBottom" };
                    return (this._nextTop >= _Overlay._Overlay._keyboardInfo._visibleDocTop &&
                            this._nextTop + flyout.height <= _Overlay._Overlay._keyboardInfo._visibleDocBottom);
                },

                _fitLeft: function Flyout_fitLeft(anchor, flyout) {
                    this._nextLeft = anchor.left - flyout.width;
                    this._nextAnimOffset = { top: "0px", left: "50px", keyframe: "WinJS-showFlyoutLeft" };
                    return (this._nextLeft >= 0 && this._nextLeft + flyout.width <= _Overlay._Overlay._keyboardInfo._visualViewportWidth);
                },

                _fitRight: function Flyout_fitRight(anchor, flyout) {
                    this._nextLeft = anchor.right;
                    this._nextAnimOffset = { top: "0px", left: "-50px", keyframe: "WinJS-showFlyoutRight" };
                    return (this._nextLeft >= 0 && this._nextLeft + flyout.width <= _Overlay._Overlay._keyboardInfo._visualViewportWidth);
                },

                _centerVertically: function Flyout_centerVertically(anchor, flyout) {
                    this._nextTop = anchor.top + anchor.height / 2 - flyout.height / 2;
                    if (this._nextTop < _Overlay._Overlay._keyboardInfo._visibleDocTop) {
                        this._nextTop = _Overlay._Overlay._keyboardInfo._visibleDocTop;
                    } else if (this._nextTop + flyout.height >= _Overlay._Overlay._keyboardInfo._visibleDocBottom) {
                        // Flag to put on bottom
                        this._nextTop = -1;
                    }
                },

                _centerHorizontally: function Flyout_centerHorizontally(anchor, flyout, alignment) {
                    if (alignment === "center") {
                        this._nextLeft = anchor.left + anchor.width / 2 - flyout.width / 2;
                    } else if (alignment === "left") {
                        this._nextLeft = anchor.left;
                    } else if (alignment === "right") {
                        this._nextLeft = anchor.right - flyout.width;
                    } else {
                        throw new _ErrorFromName("WinJS.UI.Flyout.BadAlignment", strings.badAlignment);
                    }
                    if (this._nextLeft < 0) {
                        this._nextLeft = 0;
                    } else if (this._nextLeft + flyout.width >= _Global.document.documentElement.clientWidth) {
                        // flag to put on right
                        this._nextLeft = -1;
                    }
                },

                _updateAdjustments: function Flyout_updateAdjustments(alignment) {
                    // Move to 0,0 in case it is off screen, so that it lays out at a reasonable size
                    this._element.style.top = "0px";
                    this._element.style.bottom = "auto";
                    this._element.style.left = "0px";
                    this._element.style.right = "auto";

                    // Scrolling may not be necessary
                    _ElementUtilities.removeClass(this._element, _Constants.scrollsClass);
                    if (this._lastMaxHeight !== null) {
                        this._element.style.maxHeight = this._lastMaxHeight;
                        this._lastMaxHeight = null;
                    }
                    // Alignment
                    if (alignment === "center") {
                        _ElementUtilities.removeClass(this._element, "win-leftalign");
                        _ElementUtilities.removeClass(this._element, "win-rightalign");
                    } else if (alignment === "left") {
                        _ElementUtilities.addClass(this._element, "win-leftalign");
                        _ElementUtilities.removeClass(this._element, "win-rightalign");
                    } else if (alignment === "right") {
                        _ElementUtilities.addClass(this._element, "win-rightalign");
                        _ElementUtilities.removeClass(this._element, "win-leftalign");
                    }
                },

                _showingKeyboard: function Flyout_showingKeyboard(event) {
                    if (this.hidden) {
                        return;
                    }

                    // The only way that we can be showing a keyboard when a flyout is up is because the input was
                    // in the flyout itself, in which case we'll be moving ourselves.  There is no practical way
                    // for the application to override this as the focused element is in our flyout.
                    event.ensuredFocusedElementInView = true;

                    // See if the keyboard is going to force us to move
                    this._checkKeyboardFit();

                    if (this._keyboardMovedUs) {
                        // Pop out immediately, then move to new spot
                        this._element.style.opacity = 0;
                        var that = this;
                        _Global.setTimeout(function () { that._adjustForKeyboard(); that._baseAnimateIn(); }, _Overlay._Overlay._keyboardInfo._animationShowLength);
                    }
                },

                _resize: function Flyout_resize() {
                    // If hidden and not busy animating, then nothing to do
                    if (this.hidden && !this._animating) {
                        return;
                    }

                    // This should only happen if the IHM is dismissing,
                    // the only other way is for viewstate changes, which
                    // would dismiss any flyout.
                    if (this._needToHandleHidingKeyboard) {
                        // Hiding keyboard, update our position, giving the anchor a chance to update first.
                        var that = this;
                        _BaseUtils._setImmediate(function () { that._findPosition(); });
                        this._needToHandleHidingKeyboard = false;
                    }
                },

                _checkKeyboardFit: function Flyout_checkKeyboardFit() {
                    // Check for moving to fit keyboard:
                    // - Too Tall, above top, or below bottom.
                    var height = _ElementUtilities.getTotalHeight(this._element);
                    var viewportHeight = _Overlay._Overlay._keyboardInfo._visibleDocHeight - this._nextMarginPadding;
                    if (height > viewportHeight) {
                        // Too Tall, pin to top with max height
                        this._keyboardMovedUs = true;
                        this._scrollTop = 0;
                        this._keyboardSquishedUs = viewportHeight;
                    } else if (this._nextTop === -1) {
                        // Pinned to bottom counts as moved
                        this._keyboardMovedUs = true;
                    } else if (this._nextTop < _Overlay._Overlay._keyboardInfo._visibleDocTop) {
                        // Above the top of the viewport
                        this._scrollTop = 0;
                        this._keyboardMovedUs = true;
                    } else if (this._nextBottom > _Overlay._Overlay._keyboardInfo._visibleDocBottom) {
                        // Below the bottom of the viewport
                        this._scrollTop = -1;
                        this._keyboardMovedUs = true;
                    }
                },

                _adjustForKeyboard: function Flyout_adjustForKeyboard() {
                    // Keyboard moved us, update our metrics as needed
                    if (this._keyboardSquishedUs) {
                        // Add scrollbar if we didn't already have scrollsClass
                        if (!this._hasScrolls) {
                            _ElementUtilities.addClass(this._element, _Constants.scrollsClass);
                            this._lastMaxHeight = this._element.style.maxHeight;
                        }
                        // Adjust height
                        this._element.style.maxHeight = this._keyboardSquishedUs + "px";
                    }

                    // Update top/bottom
                    this._checkScrollPosition(true);
                },

                _hidingKeyboard: function Flyout_hidingKeyboard() {
                    // If we aren't visible and not animating, or haven't been repositioned, then nothing to do
                    // We don't know if the keyboard moved the anchor, so _keyboardMovedUs doesn't help here
                    if (this.hidden && !this._animating) {
                        return;
                    }

                    // Snap to the final position
                    // We'll either just reveal the current space or resize the window
                    if (_Overlay._Overlay._keyboardInfo._isResized) {
                        // Flag resize that we'll need an updated position
                        this._needToHandleHidingKeyboard = true;
                    } else {
                        // Not resized, update our final position, giving the anchor a chance to update first.
                        var that = this;
                        _BaseUtils._setImmediate(function () { that._findPosition(); });
                    }
                },

                _checkScrollPosition: function Flyout_checkScrollPosition(showing) {
                    if (this.hidden && !showing) {
                        return;
                    }

                    // May need to adjust top by viewport offset
                    if (this._scrollTop < 0) {
                        // Need to attach to bottom
                        this._element.style.bottom = _Overlay._Overlay._keyboardInfo._visibleDocBottomOffset + "px";
                        this._element.style.top = "auto";
                    } else {
                        // Normal, attach to top
                        this._element.style.top =  _Overlay._Overlay._keyboardInfo._visibleDocTop + "px";
                        this._element.style.bottom = "auto";
                    }
                },

                // AppBar flyout animations
                _flyoutAnimateIn: function Flyout_flyoutAnimateIn() {
                    if (this._keyboardMovedUs) {
                        return this._baseAnimateIn();
                    } else {
                        this._element.style.opacity = 1;
                        this._element.style.visibility = "visible";
                        return Animations.showPopup(this._element, this._nextAnimOffset);
                    }
                },

                _flyoutAnimateOut: function Flyout_flyoutAnimateOut() {
                    if (this._keyboardMovedUs) {
                        return this._baseAnimateOut();
                    } else {
                        this._element.style.opacity = 0;
                        return Animations.hidePopup(this._element, this._nextAnimOffset);
                    }
                },

                // Hide all other flyouts besides this one
                _hideAllOtherFlyouts: function Flyout_hideAllOtherFlyouts(thisFlyout) {
                    var flyouts = _Global.document.querySelectorAll("." + _Constants.flyoutClass);
                    for (var i = 0; i < flyouts.length; i++) {
                        var flyoutControl = flyouts[i].winControl;
                        if (flyoutControl && !flyoutControl.hidden && (flyoutControl !== thisFlyout)) {
                            flyoutControl.hide();
                        }
                    }
                },

                // Returns true if there is a flyout in the DOM that is not hidden
                _isThereVisibleFlyout: function Flyout_isThereVisibleFlyout() {
                    var flyouts = _Global.document.querySelectorAll("." + _Constants.flyoutClass);
                    for (var i = 0; i < flyouts.length; i++) {
                        var flyoutControl = flyouts[i].winControl;
                        if (flyoutControl && !flyoutControl.hidden) {
                            return true;
                        }
                    }

                    return false;
                },

                _handleKeyDown: function Flyout_handleKeyDown(event) {
                    // Escape closes flyouts but if the user has a text box with an IME candidate
                    // window open, we want to skip the ESC key event since it is handled by the IME.
                    // When the IME handles a key it sets event.keyCode === Key.IME for an easy check.
                    if (event.keyCode === Key.escape && event.keyCode !== Key.IME) {
                        // Show a focus rect on what we move focus to
                        event.preventDefault();
                        event.stopPropagation();
                        this.winControl._keyboardInvoked = true;
                        this.winControl._hide();
                    } else if ((event.keyCode === Key.space || event.keyCode === Key.enter)
                         && (this === _Global.document.activeElement)) {
                        event.preventDefault();
                        event.stopPropagation();
                        this.winControl.hide();
                    } else if (event.shiftKey && event.keyCode === Key.tab
                          && this === _Global.document.activeElement
                          && !event.altKey && !event.ctrlKey && !event.metaKey) {
                        event.preventDefault();
                        event.stopPropagation();
                        this.winControl._focusOnLastFocusableElementOrThis();
                    }
                },

                // Create and add a new first div as the first child
                _addFirstDiv: function Flyout_addFirstDiv() {
                    var firstDiv = _Global.document.createElement("div");
                    firstDiv.className = _Constants.firstDivClass;
                    firstDiv.style.display = "inline";
                    firstDiv.setAttribute("role", "menuitem");
                    firstDiv.setAttribute("aria-hidden", "true");

                    // add to beginning
                    if (this._element.children[0]) {
                        this._element.insertBefore(firstDiv, this._element.children[0]);
                    } else {
                        this._element.appendChild(firstDiv);
                    }

                    var that = this;
                    _ElementUtilities._addEventListener(firstDiv, "focusin", function () { that._focusOnLastFocusableElementOrThis(); }, false);

                    return firstDiv;
                },

                // Create and add a new final div as the last child
                _addFinalDiv: function Flyout_addFinalDiv() {
                    var finalDiv = _Global.document.createElement("div");
                    finalDiv.className = _Constants.finalDivClass;
                    finalDiv.style.display = "inline";
                    finalDiv.setAttribute("role", "menuitem");
                    finalDiv.setAttribute("aria-hidden", "true");

                    this._element.appendChild(finalDiv);
                    var that = this;
                    _ElementUtilities._addEventListener(finalDiv, "focusin", function () { that._focusOnFirstFocusableElementOrThis(); }, false);

                    return finalDiv;
                },

                _writeProfilerMark: function Flyout_writeProfilerMark(text) {
                    _WriteProfilerMark("WinJS.UI.Flyout:" + this._id + ":" + text);
                }
            });
            return Flyout;
        })
    });

});
// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Glyph Enumeration
/// <dictionary>Segoe</dictionary>
define('WinJS/Controls/AppBar/_Icon',[
     'exports',
     '../../Core/_Base',
     '../../Core/_Resources'
     ], function appBarIconInit(exports, _Base, _Resources) {
    "use strict";

    var glyphs = ["previous",
                    "next",
                    "play",
                    "pause",
                    "edit",
                    "save",
                    "clear",
                    "delete",
                    "remove",
                    "add",
                    "cancel",
                    "accept",
                    "more",
                    "redo",
                    "undo",
                    "home",
                    "up",
                    "forward",
                    "right",
                    "back",
                    "left",
                    "favorite",
                    "camera",
                    "settings",
                    "video",
                    "sync",
                    "download",
                    "mail",
                    "find",
                    "help",
                    "upload",
                    "emoji",
                    "twopage",
                    "leavechat",
                    "mailforward",
                    "clock",
                    "send",
                    "crop",
                    "rotatecamera",
                    "people",
                    "closepane",
                    "openpane",
                    "world",
                    "flag",
                    "previewlink",
                    "globe",
                    "trim",
                    "attachcamera",
                    "zoomin",
                    "bookmarks",
                    "document",
                    "protecteddocument",
                    "page",
                    "bullets",
                    "comment",
                    "mail2",
                    "contactinfo",
                    "hangup",
                    "viewall",
                    "mappin",
                    "phone",
                    "videochat",
                    "switch",
                    "contact",
                    "rename",
                    "pin",
                    "musicinfo",
                    "go",
                    "keyboard",
                    "dockleft",
                    "dockright",
                    "dockbottom",
                    "remote",
                    "refresh",
                    "rotate",
                    "shuffle",
                    "list",
                    "shop",
                    "selectall",
                    "orientation",
                    "import",
                    "importall",
                    "browsephotos",
                    "webcam",
                    "pictures",
                    "savelocal",
                    "caption",
                    "stop",
                    "showresults",
                    "volume",
                    "repair",
                    "message",
                    "page2",
                    "calendarday",
                    "calendarweek",
                    "calendar",
                    "characters",
                    "mailreplyall",
                    "read",
                    "link",
                    "accounts",
                    "showbcc",
                    "hidebcc",
                    "cut",
                    "attach",
                    "paste",
                    "filter",
                    "copy",
                    "emoji2",
                    "important",
                    "mailreply",
                    "slideshow",
                    "sort",
                    "manage",
                    "allapps",
                    "disconnectdrive",
                    "mapdrive",
                    "newwindow",
                    "openwith",
                    "contactpresence",
                    "priority",
                    "uploadskydrive",
                    "gototoday",
                    "font",
                    "fontcolor",
                    "contact2",
                    "folder",
                    "audio",
                    "placeholder",
                    "view",
                    "setlockscreen",
                    "settile",
                    "cc",
                    "stopslideshow",
                    "permissions",
                    "highlight",
                    "disableupdates",
                    "unfavorite",
                    "unpin",
                    "openlocal",
                    "mute",
                    "italic",
                    "underline",
                    "bold",
                    "movetofolder",
                    "likedislike",
                    "dislike",
                    "like",
                    "alignright",
                    "aligncenter",
                    "alignleft",
                    "zoom",
                    "zoomout",
                    "openfile",
                    "otheruser",
                    "admin",
                    "street",
                    "map",
                    "clearselection",
                    "fontdecrease",
                    "fontincrease",
                    "fontsize",
                    "cellphone",
                    "reshare",
                    "tag",
                    "repeatone",
                    "repeatall",
                    "outlinestar",
                    "solidstar",
                    "calculator",
                    "directions",
                    "target",
                    "library",
                    "phonebook",
                    "memo",
                    "microphone",
                    "postupdate",
                    "backtowindow",
                    "fullscreen",
                    "newfolder",
                    "calendarreply",
                    "unsyncfolder",
                    "reporthacked",
                    "syncfolder",
                    "blockcontact",
                    "switchapps",
                    "addfriend",
                    "touchpointer",
                    "gotostart",
                    "zerobars",
                    "onebar",
                    "twobars",
                    "threebars",
                    "fourbars",
                    "scan",
                    "preview"];

    // Provide properties to grab resources for each of the icons
    /// <summary locid="WinJS.UI.AppBarIcon">
    /// The AppBarIcon enumeration provides a set of glyphs for use with the AppBarCommand icon property.
    /// </summary>
    var icons = glyphs.reduce(function (fixedIcons, item) {
       fixedIcons[item] = { get: function () { return _Resources._getWinJSString("ui/appBarIcons/" + item).value; } };
       return fixedIcons;
     }, {});

    _Base.Namespace._moduleDefine(exports, "WinJS.UI.AppBarIcon", icons);
});
// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// AppBarCommand
/// <dictionary>appbar,appbars,Flyout,Flyouts,onclick,Statics</dictionary>
define('WinJS/Controls/AppBar/_Command',[
    'exports',
    '../../Core/_Global',
    '../../Core/_WinRT',
    '../../Core/_Base',
    '../../Core/_ErrorFromName',
    '../../Core/_Resources',
    '../../Utilities/_Control',
    '../../Utilities/_Dispose',
    '../../Utilities/_ElementUtilities',
    '../Flyout/_Overlay',
    '../Tooltip',
    './_Constants',
    './_Icon'
    ], function appBarCommandInit(exports, _Global, _WinRT, _Base, _ErrorFromName, _Resources, _Control, _Dispose, _ElementUtilities, _Overlay, Tooltip, _Constants, _Icon) {
    "use strict";

    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {
        /// <field>
        /// <summary locid="WinJS.UI.AppBarCommand">
        /// Represents a command to display in an AppBar.
        /// </summary>
        /// </field>
        /// <icon src="ui_winjs.ui.appbarcommand.12x12.png" width="12" height="12" />
        /// <icon src="ui_winjs.ui.appbarcommand.16x16.png" width="16" height="16" />
        /// <htmlSnippet><![CDATA[<button data-win-control="WinJS.UI.AppBarCommand" data-win-options="{type:'button',label:'Button'}"></button>]]></htmlSnippet>
        /// <part name="appBarCommand" class="win-command" locid="WinJS.UI.AppBarCommand_part:appBarCommand">The AppBarCommand control itself.</part>
        /// <part name="appBarCommandIcon" class="win-commandicon" locid="WinJS.UI.AppBarCommand_part:appBarCommandIcon">The AppBarCommand's icon box.</part>
        /// <part name="appBarCommandImage" class="win-commandimage" locid="WinJS.UI.AppBarCommand_part:appBarCommandImage">The AppBarCommand's icon's image formatting.</part>
        /// <part name="appBarCommandRing" class="win-commandring" locid="WinJS.UI.AppBarCommand_part:appBarCommandRing">The AppBarCommand's icon's ring.</part>
        /// <part name="appBarCommandLabel" class="win-label" locid="WinJS.UI.AppBarCommand_part:appBarCommandLabel">The AppBarCommand's label</part>
        /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
        /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
        /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
        AppBarCommand: _Base.Namespace._lazy(function () {


            function _handleClick(event) {
                /*jshint validthis: true */
                var command = this.winControl;
                if (command) {
                    if (command._type === _Constants.typeToggle) {
                        command.selected = !command.selected;
                    } else if (command._type === _Constants.typeFlyout && command._flyout) {
                        var parentAppBar = _Overlay._Overlay._getParentControlUsingClassName(this, _Constants.appBarClass);
                        var placement = "top";
                        if (parentAppBar && parentAppBar.placement === "top") {
                            placement = "bottom";
                        }
                        var flyout = command._flyout;
                        // Flyout may not have processAll'd, so this may be a DOM object
                        if (typeof flyout === "string") {
                            flyout = _Global.document.getElementById(flyout);
                        }
                        if (!flyout.show) {
                            flyout = flyout.winControl;
                        }
                        if (flyout && flyout.show) {
                            flyout.show(this, placement);
                        }
                    }
                    if (command.onclick) {
                        command.onclick(event);
                    }
                }
            }

            var strings = {
                get ariaLabel() { return _Resources._getWinJSString("ui/appBarCommandAriaLabel").value; },
                get duplicateConstruction() { return "Invalid argument: Controls may only be instantiated one time for each DOM element"; },
                get badClick() { return "Invalid argument: The onclick property for an {0} must be a function"; },
                get badDivElement() { return "Invalid argument: For a content command, the element must be null or a div element"; },
                get badHrElement() { return "Invalid argument: For a separator, the element must be null or an hr element"; },
                get badButtonElement() { return "Invalid argument: For a button, toggle, or flyout command, the element must be null or a button element"; }
            };

            return _Base.Class.define(function AppBarCommand_ctor(element, options) {
                /// <signature helpKeyword="WinJS.UI.AppBarCommand.AppBarCommand">
                /// <summary locid="WinJS.UI.AppBarCommand.constructor">
                /// Creates a new AppBarCommand control.
                /// </summary>
                /// <param name="element" domElement="true" locid="WinJS.UI.AppBarCommand.constructor_p:element">
                /// The DOM element that will host the control. AppBarCommand will create one if null.
                /// </param>
                /// <param name="options" type="Object" locid="WinJS.UI.AppBarCommand.constructor_p:options">
                /// The set of properties and values to apply to the new AppBarCommand.
                /// </param>
                /// <returns type="WinJS.UI.AppBarCommand" locid="WinJS.UI.AppBarCommand.constructor_returnValue">
                /// The new AppBarCommand control.
                /// </returns>
                /// </signature>

                // Check to make sure we weren't duplicated
                if (element && element.winControl) {
                    throw new _ErrorFromName("WinJS.UI.AppBarCommand.DuplicateConstruction", strings.duplicateConstruction);
                }

                this._disposed = false;

                // Don't blow up if they didn't pass options
                if (!options) {
                    options = {};
                }

                // Need a type before we can create our element
                if (!options.type) {
                    this._type = _Constants.typeButton;
                }

                options.section = options.section || _Constants.sectionGlobal;

                // Go ahead and create it, separator and content types look different than buttons
                // Don't forget to use passed in element if one was provided.
                this._element = element;

                if (options.type === _Constants.typeContent) {
                    this._createContent();
                } else if (options.type === _Constants.typeSeparator) {
                    this._createSeparator();
                } else {
                    // This will also set the icon & label
                    this._createButton();
                }
                _ElementUtilities.addClass(this._element, "win-disposable");

                // Remember ourselves
                this._element.winControl = this;

                // Attach our css class
                _ElementUtilities.addClass(this._element, _Constants.appBarCommandClass);

                if (options.onclick) {
                    this.onclick = options.onclick;
                }
                // We want to handle some clicks
                options.onclick = _handleClick;

                _Control.setOptions(this, options);

                if (this._type === _Constants.typeToggle && !options.selected) {
                    this.selected = false;
                }

                // Set up pointerdown handler and clean up ARIA if needed
                if (this._type !== _Constants.typeSeparator) {

                    // Hide the modern focus rect on click or touch
                    var that = this;
                    _ElementUtilities._addEventListener(this._element, "pointerdown", function () { _Overlay._Overlay._addHideFocusClass(that._element); }, false);

                    // Make sure we have an ARIA role
                    var role = this._element.getAttribute("role");
                    if (role === null || role === "" || role === undefined) {
                        if (this._type === _Constants.typeToggle) {
                            role = "menuitemcheckbox";
                        } else if (this._type === _Constants.typeContent) {
                            role = "group";
                        } else {
                            role = "menuitem";
                        }
                        this._element.setAttribute("role", role);
                        if (this._type === _Constants.typeFlyout) {
                            this._element.setAttribute("aria-haspopup", true);
                        }
                    }
                    // Label should've been set by label, but if it was missed for some reason:
                    var label = this._element.getAttribute("aria-label");
                    if (label === null || label === "" || label === undefined) {
                        this._element.setAttribute("aria-label", strings.ariaLabel);
                    }
                }
            }, {
                /// <field type="String" locid="WinJS.UI.AppBarCommand.id" helpKeyword="WinJS.UI.AppBarCommand.id" isAdvanced="true">
                /// Gets or sets the ID of the AppBarCommand.
                /// </field>
                id: {
                    get: function () {
                        return this._element.id;
                    },

                    set: function (value) {
                        // we allow setting first time only. otherwise we ignore it.
                        if (value && !this._element.id) {
                            this._element.id = value;
                        }
                    }
                },

                /// <field type="String" defaultValue="button" readonly="true" oamOptionsDatatype="WinJS.UI.AppBarCommand.type" locid="WinJS.UI.AppBarCommand.type" helpKeyword="WinJS.UI.AppBarCommand.type" isAdvanced="true">
                /// Gets or sets the type of the AppBarCommand. Possible values are "button", "toggle", "flyout", "content" or "separator".
                /// </field>
                type: {
                    get: function () {
                        return this._type;
                    },
                    set: function (value) {
                        // we allow setting first time only. otherwise we ignore it.
                        if (!this._type) {
                            if (value !== _Constants.typeContent && value !== _Constants.typeFlyout && value !== _Constants.typeToggle && value !== _Constants.typeSeparator) {
                                this._type = _Constants.typeButton;
                            } else {
                                this._type = value;
                            }
                        }
                    }
                },

                /// <field type="String" locid="WinJS.UI.AppBarCommand.label" helpKeyword="WinJS.UI.AppBarCommand.label">
                /// Gets or sets the label of the AppBarCommand.
                /// </field>
                label: {
                    get: function () {
                        return this._label;
                    },
                    set: function (value) {
                        this._label = value;
                        if (this._labelSpan) {
                            this._labelSpan.textContent = this.label;
                        }

                        // Ensure that we have a tooltip, by updating already-constructed tooltips.  Separators won't have these:
                        if (!this.tooltip && this._tooltipControl) {
                            this._tooltip = this.label;
                            this._tooltipControl.innerHTML = this.label;
                        }

                        // Update aria-label
                        this._element.setAttribute("aria-label", this.label);

                        // Check if we need to suppress the tooltip
                        this._testIdenticalTooltip();
                    }
                },

                /// <field type="String" locid="WinJS.UI.AppBarCommand.icon" helpKeyword="WinJS.UI.AppBarCommand.icon">
                /// Gets or sets the icon of the AppBarCommand.
                /// </field>
                icon: {
                    get: function () {
                        return this._icon;
                    },
                    set: function (value) {

                        this._icon = _Icon[value] || value;

                        if (this._imageSpan) {
                            // If the icon's a single character, presume a glyph
                            if (this._icon && this._icon.length === 1) {
                                // Set the glyph
                                this._imageSpan.textContent = this._icon;
                                this._imageSpan.style.backgroundImage = "";
                                this._imageSpan.style.msHighContrastAdjust = "";
                            } else {
                                // Must be an image, set that
                                this._imageSpan.textContent = "";
                                this._imageSpan.style.backgroundImage = this._icon;
                                this._imageSpan.style.msHighContrastAdjust = "none";
                            }
                        }
                    }
                },

                /// <field type="Function" locid="WinJS.UI.AppBarCommand.onclick" helpKeyword="WinJS.UI.AppBarCommand.onclick">
                /// Gets or sets the function to invoke when the command is clicked.
                /// </field>
                onclick: {
                    get: function () {
                        return this._onclick;
                    },
                    set: function (value) {
                        if (value && typeof value !== "function") {
                            throw new _ErrorFromName("WinJS.UI.AppBarCommand.BadClick", _Resources._formatString(strings.badClick, "AppBarCommand"));
                        }
                        this._onclick = value;
                    }
                },

                /// <field type="Object" locid="WinJS.UI.AppBarCommand.flyout" helpKeyword="WinJS.UI.AppBarCommand.flyout">
                /// For flyout-type AppBarCommands, this property returns the WinJS.UI.Flyout that this command invokes.
                /// When setting this property, you may also use the String ID of the flyout to invoke, the DOM object
                /// for the flyout, or the WinJS.UI.Flayout object itself.
                /// </field>
                flyout: {
                    get: function () {
                        // Resolve it to the flyout
                        var flyout = this._flyout;
                        if (typeof flyout === "string") {
                            flyout = _Global.document.getElementById(flyout);
                        }
                        // If it doesn't have a .element, then we need to getControl on it
                        if (flyout && !flyout.element) {
                            flyout = flyout.winControl;
                        }

                        return flyout;
                    },
                    set: function (value) {
                        // Need to update aria-owns with the new ID.
                        var id = value;
                        if (id && typeof id !== "string") {
                            // Our controls have .element properties
                            if (id.element) {
                                id = id.element;
                            }
                            // Hope it's a DOM element, get ID from DOM element
                            if (id) {
                                if (id.id) {
                                    id = id.id;
                                } else {
                                    // No id, have to fake one
                                    id.id = _ElementUtilities._uniqueID(id);
                                    id = id.id;
                                }
                            }
                        }
                        if (typeof id === "string") {
                            this._element.setAttribute("aria-owns", id);
                        }

                        // Remember it
                        this._flyout = value;
                    }
                },

                /// <field type="String" defaultValue="global" oamOptionsDatatype="WinJS.UI.AppBarCommand.section" locid="WinJS.UI.AppBarCommand.section" helpKeyword="WinJS.UI.AppBarCommand.section">
                /// Gets or sets the section that the AppBarCommand is in. Possible values are "selection" and "global".
                /// </field>
                section: {
                    get: function () {
                        return this._section;
                    },
                    set: function (value) {
                        // we allow settings section only one time
                        if (!this._section || _WinRT.Windows.ApplicationModel.DesignMode.designModeEnabled) {
                            this._setSection(value);
                        }
                    }
                },

                /// <field type="String" locid="WinJS.UI.AppBarCommand.tooltip" helpKeyword="WinJS.UI.AppBarCommand.tooltip">Gets or sets the tooltip text of the AppBarCommand.</field>
                tooltip: {
                    get: function () {
                        return this._tooltip;
                    },
                    set: function (value) {
                        this._tooltip = value;

                        // Update already-constructed tooltips. Separators and content commands won't have these:
                        if (this._tooltipControl) {
                            this._tooltipControl.innerHTML = this._tooltip;
                        }

                        // Check if we need to suppress the tooltip
                        this._testIdenticalTooltip();
                    }
                },

                /// <field type="Boolean" locid="WinJS.UI.AppBarCommand.selected" helpKeyword="WinJS.UI.AppBarCommand.selected">Set or get the selected state of a toggle button.</field>
                selected: {
                    get: function () {
                        // Ensure it's a boolean because we're using the DOM element to keep in-sync
                        return this._element.getAttribute("aria-checked") === "true";
                    },
                    set: function (value) {
                        this._element.setAttribute("aria-checked", value);
                    }
                },

                /// <field type="HTMLElement" domElement="true" readonly="true" hidden="true" locid="WinJS.UI.AppBarCommand.element" helpKeyword="WinJS.UI.AppBarCommand.element">
                /// The DOM element that hosts the AppBarCommad.
                /// </field>
                element: {
                    get: function () {
                        return this._element;
                    }
                },

                /// <field type="Boolean" locid="WinJS.UI.AppBarCommand.disabled" helpKeyword="WinJS.UI.AppBarCommand.disabled">
                /// Gets or sets a value that indicates whether the AppBarCommand is disabled. A value of true disables the AppBarCommand, and a value of false enables it.
                /// </field>
                disabled: {
                    get: function () {
                        // Ensure it's a boolean because we're using the DOM element to keep in-sync
                        return !!this._element.disabled;
                    },
                    set: function (value) {
                        this._element.disabled = value;
                    }
                },

                /// <field type="Boolean" hidden="true" locid="WinJS.UI.AppBarCommand.hidden" helpKeyword="WinJS.UI.AppBarCommand.hidden">
                /// Gets a value that indicates whether the AppBarCommand is hiding or in the process of becoming hidden.
                /// A value of true indicates that the AppBarCommand is hiding or in the process of becoming hidden.
                /// </field>
                hidden: {
                    get: function () {
                        // Ensure it's a boolean because we're using the DOM element to keep in-sync
                        return this._element.style.visibility === "hidden";
                    },
                    set: function (value) {
                        var appbarControl = _Overlay._Overlay._getParentControlUsingClassName(this._element, _Constants.appBarClass);
                        if (appbarControl && !appbarControl.hidden) {
                            throw new _ErrorFromName("WinJS.UI.AppBarCommand.CannotChangeHiddenProperty", _Resources._formatString(_Overlay._Overlay.commonstrings.cannotChangeHiddenProperty, "AppBar"));
                        }

                        if (value === this.hidden) {
                            // No changes to make.
                            return;
                        }

                        var style = this._element.style;

                        if (value) {
                            style.visibility = "hidden";
                            style.display = "none";
                        } else {
                            style.visibility = "";
                            style.display = "inline-block";
                        }
                        if (appbarControl) {
                            appbarControl._commandsUpdated();
                        }
                    }
                },

                /// <field type="HTMLElement" domElement="true" locid="WinJS.UI.AppBarCommand.firstElementFocus" helpKeyword="WinJS.UI.AppBarCommand.firstElementFocus">
                /// Gets or sets the HTMLElement within a "content" type AppBarCommand that should receive focus whenever focus moves via Home or the arrow keys,
                /// from the previous AppBarCommand to the this AppBarCommand. Returns the AppBarCommand object's host element by default.
                /// </field>
                firstElementFocus: {
                    get: function () {
                        return this._firstElementFocus || this._lastElementFocus || this._element;
                    },
                    set: function (element) {
                        // Arguments of null and this.element should treated the same to ensure that this.element is never a tabstop when either focus property has been set.
                        this._firstElementFocus = (element === this.element) ? null : element;
                        this._updateTabStop();
                    }
                },

                /// <field type="HTMLElement" domElement="true" locid="WinJS.UI.AppBarCommand.lastElementFocus" helpKeyword="WinJS.UI.AppBarCommand.lastElementFocus">
                /// Gets or sets the HTMLElement within a "content" type AppBarCommand that should receive focus whenever focus would move, via End or arrow keys,
                /// from the next AppBarCommand to this AppBarCommand. Returns this AppBarCommand object's host element by default.
                /// </field>
                lastElementFocus: {
                    get: function () {
                        return this._lastElementFocus || this._firstElementFocus || this._element;
                    },
                    set: function (element) {
                        // Arguments of null and this.element should treated the same to ensure that this.element is never a tabstop when either focus property has been set.
                        this._lastElementFocus = (element === this.element) ? null : element;
                        this._updateTabStop();
                    }
                },

                dispose: function () {
                    /// <signature helpKeyword="WinJS.UI.AppBarCommand.dispose">
                    /// <summary locid="WinJS.UI.AppBarCommand.dispose">
                    /// Disposes this control.
                    /// </summary>
                    /// </signature>
                    if (this._disposed) {
                        return;
                    }
                    this._disposed = true;

                    if (this._tooltipControl) {
                        this._tooltipControl.dispose();
                    }

                    if (this._type === _Constants.typeContent) {
                        _Dispose.disposeSubTree(this.element);
                    }
                },

                addEventListener: function (type, listener, useCapture) {
                    /// <signature helpKeyword="WinJS.UI.AppBarCommand.addEventListener">
                    /// <summary locid="WinJS.UI.AppBarCommand.addEventListener">
                    /// Registers an event handler for the specified event.
                    /// </summary>
                    /// <param name="type" type="String" locid="WinJS.UI.AppBarCommand.addEventListener_p:type">
                    /// Required. The name of the event to register. It must be "beforeshow", "beforehide", "aftershow", or "afterhide".
                    /// </param>
                    /// <param name="listener" type="Function" locid="WinJS.UI.AppBarCommand.addEventListener_p:listener">Required. The event handler function to associate with this event.</param>
                    /// <param name="useCapture" type="Boolean" locid="WinJS.UI.AppBarCommand.addEventListener_p:useCapture">
                    /// Optional. Set to true to register the event handler for the capturing phase; otherwise, set to false to register the event handler for the bubbling phase.
                    /// </param>
                    /// </signature>
                    return this._element.addEventListener(type, listener, useCapture);
                },

                removeEventListener: function (type, listener, useCapture) {
                    /// <signature helpKeyword="WinJS.UI.AppBarCommand.removeEventListener">
                    /// <summary locid="WinJS.UI.AppBarCommand.removeEventListener">
                    /// Removes an event handler that the addEventListener method registered.
                    /// </summary>
                    /// <param name="type" type="String" locid="WinJS.UI.AppBarCommand.removeEventListener_p:type">Required. The name of the event to remove.</param>
                    /// <param name="listener" type="Function" locid="WinJS.UI.AppBarCommand.removeEventListener_p:listener">Required. The event handler function to remove.</param>
                    /// <param name="useCapture" type="Boolean" locid="WinJS.UI.AppBarCommand.removeEventListener_p:useCapture">
                    /// Optional. Set to true to remove the capturing phase event handler; otherwise, set to false to remove the bubbling phase event handler.
                    /// </param>
                    /// </signature>
                    return this._element.removeEventListener(type, listener, useCapture);
                },

                /// <field type="String" locid="WinJS.UI.AppBarCommand.extraClass" helpKeyword="WinJS.UI.AppBarCommand.extraClass" isAdvanced="true">Adds an extra CSS class during construction.</field>
                extraClass: {
                    get: function () {
                        return this._extraClass;
                    },
                    set: function (value) {
                        if (this._extraClass) {
                            _ElementUtilities.removeClass(this._element, this._extraClass);
                        }
                        this._extraClass = value;
                        _ElementUtilities.addClass(this._element, this._extraClass);
                    }
                },

                // Private
                _testIdenticalTooltip: function AppBarCommand_testIdenticalToolTip() {
                    this._hideIfFullSize = (this._label === this._tooltip);
                },

                _createContent: function AppBarCommand_createContent() {
                    // Make sure there's an element
                    if (!this._element) {
                        this._element = _Global.document.createElement("div");
                    } else {
                        // Verify the element was a div
                        if (this._element.tagName !== "DIV") {
                            throw new _ErrorFromName("WinJS.UI.AppBarCommand.BadDivElement", strings.badDivElement);
                        }
                    }

                    // If a tabIndex isnt set, default to 0;
                    if (parseInt(this._element.getAttribute("tabIndex"), 10) !== this._element.tabIndex) {
                        this._element.tabIndex = 0;
                    }
                },

                _createSeparator: function AppBarCommand_createSeparator() {
                    // Make sure there's an element
                    if (!this._element) {
                        this._element = _Global.document.createElement("hr");
                    } else {
                        // Verify the element was an hr
                        if (this._element.tagName !== "HR") {
                            throw new _ErrorFromName("WinJS.UI.AppBarCommand.BadHrElement", strings.badHrElement);
                        }
                    }
                },

                _createButton: function AppBarCommand_createButton() {
                    // Make sure there's an element
                    if (!this._element) {
                        this._element = _Global.document.createElement("button");
                    } else {
                        // Verify the element was a button
                        if (this._element.tagName !== "BUTTON") {
                            throw new _ErrorFromName("WinJS.UI.AppBarCommand.BadButtonElement", strings.badButtonElement);
                        }
                        // Make sure it has a type="button"
                        var type = this._element.getAttribute("type");
                        if (type === null || type === "" || type === undefined) {
                            this._element.setAttribute("type", "button");
                        }
                        this._element.innerHTML = "";
                    }

                    // AppBarCommand buttons need to look like this:
                    //// <button type="button" onclick="" class="win-command win-global">
                    ////      <span class="win-commandicon win-commandring"><span class="win-commandimage">&#xE0D5;</span></span><span class="win-label">Command 1</span>
                    //// Or This:
                    ////      <span class="win-commandicon win-commandring"><span class="win-commandimage" style="background-image:url('customimage.png')"></span></span><span class="win-label">Command 1</span>
                    //// </button>
                    this._element.type = "button";
                    this._iconSpan = _Global.document.createElement("span");
                    this._iconSpan.setAttribute("aria-hidden", "true");
                    this._iconSpan.className = "win-commandicon win-commandring";
                    this._iconSpan.tabIndex = -1;
                    this._element.appendChild(this._iconSpan);
                    this._imageSpan = _Global.document.createElement("span");
                    this._imageSpan.setAttribute("aria-hidden", "true");
                    this._imageSpan.className = "win-commandimage";
                    this._imageSpan.tabIndex = -1;
                    this._iconSpan.appendChild(this._imageSpan);
                    this._labelSpan = _Global.document.createElement("span");
                    this._labelSpan.setAttribute("aria-hidden", "true");
                    this._labelSpan.className = "win-label";
                    this._labelSpan.tabIndex = -1;
                    this._element.appendChild(this._labelSpan);
                    // 'win-global' or 'win-selection' are added later by caller.
                    // Label and icon are added later by caller.

                    // Attach a tooltip - Note: we're going to stomp on it's setControl so we don't have to make another DOM element to hang it off of.
                    // This private _tooltipControl attribute is used by other pieces, changing the name could break them.
                    this._tooltipControl = new Tooltip.Tooltip(this._element);
                    var that = this;
                    this._tooltipControl.addEventListener("beforeopen", function () {
                        if (that._hideIfFullSize && !_Overlay._Overlay._getParentControlUsingClassName(that._element.parentElement, _Constants.reducedClass)) {
                            that._tooltipControl.close();
                        }
                    }, false);
                },

                _setSection: function AppBarCommand_setSection(section) {
                    if (!section) {
                        section = _Constants.sectionGlobal;
                    }
                    if (this._section) {
                        // Remove the old section class
                        if (this._section === _Constants.sectionGlobal) {
                            _ElementUtilities.removeClass(this._element, _Constants.appBarCommandGlobalClass);
                        } else if (this.section === _Constants.sectionSelection) {
                            _ElementUtilities.removeClass(this._element, _Constants.appBarCommandSelectionClass);
                        }
                    }
                    // Add the new section class
                    this._section = section;
                    if (section === _Constants.sectionGlobal) {
                        _ElementUtilities.addClass(this._element, _Constants.appBarCommandGlobalClass);
                    } else if (section === _Constants.sectionSelection) {
                        _ElementUtilities.addClass(this._element, _Constants.appBarCommandSelectionClass);
                    }
                },

                _updateTabStop: function AppBarCommand_updateTabStop() {
                    // Whenever the firstElementFocus or lastElementFocus properties are set for content type AppBarCommands,
                    // the containing command element is no longer a tabstop.

                    if (this._firstElementFocus || this._lastElementFocus) {
                        this.element.tabIndex = -1;
                    } else {
                        this.element.tabIndex = 0;
                    }
                },

                _isFocusable: function AppBarCommand_isFocusable() {
                    return (!this.hidden && this._type !== _Constants.typeSeparator && !this.element.disabled &&
                        (this.firstElementFocus.tabIndex >= 0 || this.lastElementFocus.tabIndex >= 0));
                },
            });
        })
    });

});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/AppBar/_Layouts',[
    'exports',
    '../../Core/_Global',
    '../../Core/_Base',
    '../../Core/_ErrorFromName',
    '../../Core/_Resources',
    '../../Scheduler',
    '../../Utilities/_Control',
    '../../Utilities/_Dispose',
    '../../Utilities/_ElementUtilities',
    './_Command',
    './_Constants'
], function appBarLayoutsInit(exports, _Global, _Base, _ErrorFromName, _Resources, Scheduler, _Control, _Dispose, _ElementUtilities, _Command, _Constants) {
    "use strict";

    // AppBar will use this when AppBar.layout property is set to "custom"
    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {
        _AppBarBaseLayout: _Base.Namespace._lazy(function () {
            var baseType = _Constants.appBarLayoutCustom;

            var strings = {
                get nullCommand() { return "Invalid argument: command must not be null"; }
            };

            var _AppBarBaseLayout = _Base.Class.define(function _AppBarBaseLayout_ctor(appBarEl, options) {
                this._disposed = false;

                options = options || {};
                _Control.setOptions(this, options);

                if (appBarEl) {
                    this.connect(appBarEl);
                }
            }, {
                // Members
                className: {
                    get: function _AppBarBaseLayout_get_className() {
                        return this._className;
                    },
                },
                type: {
                    get: function _AppBarBaseLayout_get_className() {
                        return this._type || baseType;
                    },
                },
                commandsInOrder: {
                    get: function _AppBarBaseLayout_get_commandsInOrder() {
                        // Gets a DOM ordered Array of the AppBarCommand elements in the AppBar.
                        var commands = this.appBarEl.querySelectorAll("." + _Constants.appBarCommandClass);

                        // Needs to be an array, in case these are getting passed to a new layout.
                        // The new layout will invoke the AppBar._layoutCommands, and it expects an
                        // Array.
                        return Array.prototype.slice.call(commands);
                    }
                },
                connect: function _AppBarBaseLayout_connect(appBarEl) {
                    if (this.className) {
                        _ElementUtilities.addClass(appBarEl, this.className);
                    }
                    this.appBarEl = appBarEl;
                },
                disconnect: function _AppBarBaseLayout_disconnect() {
                    if (this.className) {
                        _ElementUtilities.removeClass(this.appBarEl, this.className);
                    }
                    this.appBarEl = null;
                    this.dispose();
                },
                layout: function _AppBarBaseLayout_layout(commands) {
                    // Append commands to the DOM.
                    var len = commands.length;
                    for (var i = 0; i < len; i++) {
                        var command = this.sanitizeCommand(commands[i]);
                        this.appBarEl.appendChild(command._element);
                    }
                },
                sanitizeCommand: function _AppBarBaseLayout_sanitizeCommand(command) {
                    if (!command) {
                        throw new _ErrorFromName("WinJS.UI.AppBar.NullCommand", strings.nullCommand);
                    }
                    // See if it's a command already
                    command = command.winControl || command;
                    if (!command._element) {
                        // Not a command, so assume it is options for the command's constructor.
                        command = new _Command.AppBarCommand(null, command);
                    }
                    // If we were attached somewhere else, detach us
                    if (command._element.parentElement) {
                        command._element.parentElement.removeChild(command._element);
                    }

                    return command;
                },
                dispose: function _AppBarBaseLayout_dispose() {
                    this._disposed = true;
                },
                disposeChildren: function _AppBarBaseLayout_disposeChildren() {
                    var appBarFirstDiv = this.appBarEl.querySelectorAll("." + _Constants.firstDivClass);
                    appBarFirstDiv = appBarFirstDiv.length >= 1 ? appBarFirstDiv[0] : null;
                    var appBarFinalDiv = this.appBarEl.querySelectorAll("." + _Constants.finalDivClass);
                    appBarFinalDiv = appBarFinalDiv.length >= 1 ? appBarFinalDiv[0] : null;

                    var children = this.appBarEl.children;
                    var length = children.length;
                    for (var i = 0; i < length; i++) {
                        var element = children[i];
                        if (element === appBarFirstDiv || element === appBarFinalDiv) {
                            continue;
                        } else {
                            _Dispose.disposeSubTree(element);
                        }
                    }
                },
                handleKeyDown: function _AppBarBaseLayout_handleKeyDown() {
                    // NOP
                },
                commandsUpdated: function _AppBarBaseLayout_commandsUpdated() {
                    // NOP
                },
                beginAnimateCommands: function _AppBarBaseLayout_beginAnimateCommands() {
                    // The parameters are 3 mutually exclusive arrays of win-command elements contained in this Overlay.
                    // 1) showCommands[]: All of the HIDDEN win-command elements that ARE scheduled to show.
                    // 2) hideCommands[]: All of the VISIBLE win-command elements that ARE scheduled to hide.
                    // 3) otherVisibleCommands[]: All VISIBLE win-command elements that ARE NOT scheduled to hide.

                    // NOP
                },
                endAnimateCommands: function _AppBarBaseLayout_endAnimateCommands() {
                    // NOP
                },
                scale: function _AppBarBaseLayout_scale() {
                    // NOP
                },
                resize: function _AppBarBaseLayout_resize() {
                    // NOP
                },
            });
            return _AppBarBaseLayout;
        }),
    });

    // AppBar will use this when AppBar.layout property is set to "commands"
    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {
        _AppBarCommandsLayout: _Base.Namespace._lazy(function () {
            var layoutClassName = _Constants.commandLayoutClass;
            var layoutType = _Constants.appBarLayoutCommands;

            var _AppBarCommandsLayout = _Base.Class.derive(exports._AppBarBaseLayout, function _AppBarCommandsLayout_ctor(appBarEl) {
                exports._AppBarBaseLayout.call(this, appBarEl, { _className: layoutClassName, _type: layoutType });
                this._commandLayoutsInit(appBarEl);
            }, {
                _getWidthOfFullSizeCommands: function _AppBarCommandsLayout_getWidthOfFullSizeCommands(commands) {
                    // Commands layout puts primary commands and secondary commands into the primary row.
                    // Return the total width of all visible primary and secondary commands as if they were full-size.

                    // Perform any pending measurements on "content" type AppBarCommands.
                    if (this._needToMeasureNewCommands) {
                        this._measureContentCommands();
                    }
                    var accumulatedWidth = 0;
                    var separatorsCount = 0;
                    var buttonsCount = 0;

                    if (!commands) {
                        // Return the cached full size width of the last known visible commands in the AppBar.
                        return this._fullSizeWidthOfLastKnownVisibleCommands;
                    } else {
                        // Return the width of the specified commands.
                        var command;
                        for (var i = 0, len = commands.length; i < len; i++) {
                            command = commands[i].winControl || commands[i];
                            if (command._type === _Constants.typeSeparator) {
                                separatorsCount++;
                            } else if (command._type !== _Constants.typeContent) {
                                // button, toggle, and flyout types all have the same width.
                                buttonsCount++;
                            } else {
                                accumulatedWidth += command._fullSizeWidth;
                            }
                        }
                    }
                    return accumulatedWidth += (separatorsCount * _Constants.separatorWidth) + (buttonsCount * _Constants.buttonWidth);
                },
                _getFocusableCommandsInLogicalOrder: function _AppBarCommandsLayout_getCommandsInLogicalOrder() {
                    // Function returns an array of all the contained AppBarCommands which are reachable by left/right arrows.

                    var selectionCommands = this._secondaryCommands.children,
                        globalCommands = this._primaryCommands.children,
                        focusedIndex = -1;

                    var getFocusableCommandsHelper = function (commandsInReach) {
                        var focusableCommands = [];
                        for (var i = 0, len = commandsInReach.length; i < len; i++) {
                            var element = commandsInReach[i];
                            if (_ElementUtilities.hasClass(element, _Constants.appBarCommandClass) && element.winControl) {
                                var containsFocus = element.contains(_Global.document.activeElement);
                                // With the inclusion of content type commands, it may be possible to tab to elements in AppBarCommands that are not reachable by arrow keys.
                                // Regardless, when an AppBarCommand contains the element with focus, we just include the whole command so that we can determine which
                                // commands are adjacent to it when looking for the next focus destination.
                                if (element.winControl._isFocusable() || containsFocus) {
                                    focusableCommands.push(element);
                                    if (containsFocus) {
                                        focusedIndex = focusableCommands.length - 1;
                                    }
                                }
                            }
                        }
                        return focusableCommands;
                    };

                    // Determines which set of commands the user could potentially reach through Home, End, and arrow keys.
                    // All commands in the commands layout AppBar, from left to right are in reach. Selection then Global.
                    var commandsInReach = Array.prototype.slice.call(selectionCommands).concat(Array.prototype.slice.call(globalCommands));

                    var focusableCommands = getFocusableCommandsHelper(commandsInReach);
                    focusableCommands.focusedIndex = focusedIndex;
                    return focusableCommands;
                },
            });

            // Override some our base implementations and expand our API surface with the commandLayoutsMixin object.
            _Base.Class.mix(_AppBarCommandsLayout, _commandLayoutsMixin);
            return _AppBarCommandsLayout;
        }),
    });

    // These are functions and properties that any new command layout would want to share with our existing "commands" layout.
    var _commandLayoutsMixin = {
        layout: function _commandLayoutsMixin_layout(commands) {
            // Insert commands and other layout specific DOM into the AppBar element.

            // Empty our tree.
            _ElementUtilities.empty(this._primaryCommands);
            _ElementUtilities.empty(this._secondaryCommands);

            // Keep track of the order we receive the commands in.
            this._commandsInOriginalOrder = [];

            // Layout commands
            for (var i = 0, len = commands.length; i < len; i++) {
                var command = this.sanitizeCommand(commands[i]);

                this._commandsInOriginalOrder.push(command.element);

                if ("global" === command.section) {
                    this._primaryCommands.appendChild(command._element);
                } else {
                    this._secondaryCommands.appendChild(command._element);
                }
            }

            // Append layout containers to AppBar element.
            // Secondary Commands should come first in Tab Order.
            this.appBarEl.appendChild(this._secondaryCommands);
            this.appBarEl.appendChild(this._primaryCommands);


            // Need to measure all content commands after they have been added to the AppBar to make sure we allow
            // user defined CSS rules based on the ancestor of the content command to take affect.
            this._needToMeasureNewCommands = true;

            // In case this is called from the constructor before the AppBar element has been appended to the DOM,
            // we schedule the initial scaling of commands, with the expectation that the element will be added
            // synchronously, in the same block of code that called the constructor.
            Scheduler.schedule(function () {
                if (this._needToMeasureNewCommands && !this._disposed) {
                    this.scale();
                }
            }.bind(this), Scheduler.Priority.idle, this, "WinJS._commandLayoutsMixin._scaleNewCommands");

        },
        commandsInOrder: {
            get: function () {
                return this._commandsInOriginalOrder.filter(function (command) {
                    // Make sure the element is still in the AppBar.
                    return this.appBarEl.contains(command);
                }, this);
            }
        },
        disposeChildren: function _commandLayoutsMixin_disposeChildren() {
            _Dispose.disposeSubTree(this._primaryCommands);
            _Dispose.disposeSubTree(this._secondaryCommands);
        },
        handleKeyDown: function _commandLayoutsMixin_handleKeyDown(event) {
            var Key = _ElementUtilities.Key;

            if (_ElementUtilities._matchesSelector(event.target, ".win-interactive, .win-interactive *")) {
                return; // Ignore left, right, home & end keys if focused element has win-interactive class.
            }
            var rtl = _Global.getComputedStyle(this.appBarEl).direction === "rtl";
            var leftKey = rtl ? Key.rightArrow : Key.leftArrow;
            var rightKey = rtl ? Key.leftArrow : Key.rightArrow;

            if (event.keyCode === leftKey || event.keyCode === rightKey || event.keyCode === Key.home || event.keyCode === Key.end) {

                var globalCommandHasFocus = this._primaryCommands.contains(_Global.document.activeElement);
                var focusableCommands = this._getFocusableCommandsInLogicalOrder(globalCommandHasFocus);
                var targetCommand;

                if (focusableCommands.length) {
                    switch (event.keyCode) {
                        case leftKey:
                            // Arrowing past the last command wraps back around to the first command.
                            var index = Math.max(-1, focusableCommands.focusedIndex - 1) + focusableCommands.length;
                            targetCommand = focusableCommands[index % focusableCommands.length].winControl.lastElementFocus;
                            break;

                        case rightKey:
                            // Arrowing previous to the first command wraps back around to the last command.
                            var index = focusableCommands.focusedIndex + 1 + focusableCommands.length;
                            targetCommand = focusableCommands[index % focusableCommands.length].winControl.firstElementFocus;
                            break;

                        case Key.home:
                            var index = 0;
                            targetCommand = focusableCommands[index].winControl.firstElementFocus;
                            break;

                        case Key.end:
                            var index = focusableCommands.length - 1;
                            targetCommand = focusableCommands[index].winControl.lastElementFocus;
                            break;
                    }
                }

                if (targetCommand) {
                    targetCommand.focus();
                    // Prevent default so that the browser doesn't also evaluate the keydown event on the newly focused element.
                    event.preventDefault();
                }
            }
        },
        commandsUpdated: function _commandLayoutsMixin_commandsUpdated(newSetOfVisibleCommands) {
            // Whenever new commands are set or existing commands are hiding/showing in the AppBar, this
            // function is called to update the cached width measurement of all visible AppBarCommands.

            var visibleCommands = (newSetOfVisibleCommands) ? newSetOfVisibleCommands : this.commandsInOrder.filter(function (command) {
                return !command.winControl.hidden;
            });
            this._fullSizeWidthOfLastKnownVisibleCommands = this._getWidthOfFullSizeCommands(visibleCommands);
        },
        beginAnimateCommands: function _commandLayoutsMixin_beginAnimateCommands(showCommands, hideCommands, otherVisibleCommands) {
            // The parameters are 3 mutually exclusive arrays of win-command elements contained in this Overlay.
            // 1) showCommands[]: All of the HIDDEN win-command elements that ARE scheduled to show.
            // 2) hideCommands[]: All of the VISIBLE win-command elements that ARE scheduled to hide.
            // 3) otherVisibleCommands[]: All VISIBLE win-command elements that ARE NOT scheduled to hide.

            this._scaleAfterAnimations = false;

            // Determine if the overall width of visible commands in the primary row will be increasing OR decreasing.
            var changeInWidth = this._getWidthOfFullSizeCommands(showCommands) - this._getWidthOfFullSizeCommands(hideCommands);
            if (changeInWidth > 0) {
                // Width of contents is going to increase, update our command counts now, to what they will be after we complete the animations.
                var visibleCommandsAfterAnimations = otherVisibleCommands.concat(showCommands);
                this.commandsUpdated(visibleCommandsAfterAnimations);
                // Make sure we will have enough room to fit everything on a single row.
                this.scale();
            } else if (changeInWidth < 0) {
                // Width of contents is going to decrease. Once animations are complete, check if
                // there is enough available space to make the remaining commands full size.
                this._scaleAfterAnimations = true;
            }
        },
        endAnimateCommands: function _commandLayoutsMixin_endAnimateCommands() {
            if (this._scaleAfterAnimations) {
                this.commandsUpdated();
                this.scale();
            }
        },
        scale: function _commandLayoutsMixin_scale() {
            // If the total width of all AppBarCommands in the primary row is greater than the
            // width of the AppBar, add the win-reduced class to the AppBar element and all
            // AppBarCommands will reduce in size.

            // Measure the width all visible commands in  AppBar's primary row, the AppBar's offsetWidth and the AppBar horizontal padding:
            var fullSizeWidthOfVisibleContent = this._getWidthOfFullSizeCommands();
            if (this._appBarTotalKnownWidth !== +this._appBarTotalKnownWidth) {
                this._appBarTotalKnownWidth = this._scaleHelper();
            }

            if (fullSizeWidthOfVisibleContent <= this._appBarTotalKnownWidth) {
                // Full size commands
                _ElementUtilities.removeClass(this.appBarEl, _Constants.reducedClass);
            } else {
                // Reduced size commands
                _ElementUtilities.addClass(this.appBarEl, _Constants.reducedClass);
            }
        },
        resize: function _commandLayoutsMixin_resize() {
            if (!this._disposed) {
                // Check for horizontal window resizes.
                this._appBarTotalKnownWidth = null;
                if (!this.appBarEl.winControl.hidden) {
                    this.scale();
                }
            }
        },
        disconnect: function _commandLayoutsMixin_disconnect() {
            _ElementUtilities.removeClass(this.appBarEl, _Constants.reducedClass);
            exports._AppBarBaseLayout.prototype.disconnect.call(this);
        },
        _commandLayoutsInit: function _commandLayoutsMixin_commandLayoutsInit() {
            // Create layout infrastructure
            this._primaryCommands = _Global.document.createElement("DIV");
            this._secondaryCommands = _Global.document.createElement("DIV");
            _ElementUtilities.addClass(this._primaryCommands, _Constants.primaryCommandsClass);
            _ElementUtilities.addClass(this._secondaryCommands, _Constants.secondaryCommandsClass);
        },
        _scaleHelper: function _commandLayoutsMixin_scaleHelper() {
            // This exists as a single line function so that unit tests can
            // overwrite it since they can't resize the WWA window.

            // It is expected that AppBar is an immediate child of the <body> and will have 100% width.
            // We measure the clientWidth of the documentElement so that we can scale the AppBar lazily
            // even while its element is display: 'none'
            var extraPadding = this.appBarEl.winControl.closedDisplayMode === "minimal" ? _Constants.appBarInvokeButtonWidth : 0;
            return _Global.document.documentElement.clientWidth - extraPadding;
        },
        _measureContentCommands: function _commandLayoutsMixin_measureContentCommands() {
            // AppBar measures the width of content commands when they are first added
            // and then caches that value to avoid additional layouts in the future.

            // Can't measure unless We're in the document body
            if (_Global.document.body.contains(this.appBarEl)) {
                this._needToMeasureNewCommands = false;

                // Remove the reducedClass from AppBar to ensure fullsize measurements
                var hadReducedClass = _ElementUtilities.hasClass(this.appBarEl, _Constants.reducedClass);
                _ElementUtilities.removeClass(this.appBarEl, _Constants.reducedClass);

                var hadHiddenClass = _ElementUtilities.hasClass(this.appBarEl, _Constants.hiddenClass);
                _ElementUtilities.removeClass(this.appBarEl, _Constants.hiddenClass);

                // Make sure AppBar and children have width dimensions.
                var prevAppBarDisplay = this.appBarEl.style.display;
                this.appBarEl.style.display = "";
                var prevCommandDisplay;

                var contentElements = this.appBarEl.querySelectorAll("div." + _Constants.appBarCommandClass);
                var element;
                for (var i = 0, len = contentElements.length; i < len; i++) {
                    element = contentElements[i];
                    if (element.winControl && element.winControl._type === _Constants.typeContent) {
                        // Make sure command has width dimensions before we measure.
                        prevCommandDisplay = element.style.display;
                        element.style.display = "";
                        element.winControl._fullSizeWidth = _ElementUtilities.getTotalWidth(element) || 0;
                        element.style.display = prevCommandDisplay;
                    }
                }

                // Restore state to AppBar.
                this.appBarEl.style.display = prevAppBarDisplay;
                if (hadReducedClass) {
                    _ElementUtilities.addClass(this.appBarEl, _Constants.reducedClass);
                }
                if (hadHiddenClass) {
                    _ElementUtilities.addClass(this.appBarEl, _Constants.hiddenClass);
                }

                this.commandsUpdated();
            }
        },
    };
});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// AppBar
/// <dictionary>appbar,appBars,Flyout,Flyouts,iframe,Statics,unfocus,WinJS</dictionary>
define('WinJS/Controls/AppBar',[
    'exports',
    '../Core/_Global',
    '../Core/_WinRT',
    '../Core/_Base',
    '../Core/_BaseUtils',
    '../Core/_ErrorFromName',
    '../Core/_Resources',
    '../Core/_WriteProfilerMark',
    '../Animations',
    '../Promise',
    '../Scheduler',
    '../Utilities/_Control',
    '../Utilities/_Dispose',
    '../Utilities/_ElementUtilities',
    '../Utilities/_Hoverable',
    '../Utilities/_KeyboardBehavior',
    './AppBar/_Constants',
    './AppBar/_Layouts',
    './AppBar/_Command',
    './AppBar/_Icon',
    './Flyout/_Overlay',
    'require-style!less/desktop/controls',
    'require-style!less/phone/controls'
], function appBarInit(exports, _Global, _WinRT, _Base, _BaseUtils, _ErrorFromName, _Resources, _WriteProfilerMark, Animations, Promise, Scheduler, _Control, _Dispose, _ElementUtilities, _Hoverable, _KeyboardBehavior, _Constants, _Layouts, _Command, _Icon, _Overlay) {
    "use strict";

    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {
        /// <field>
        /// <summary locid="WinJS.UI.AppBar">
        /// Represents an application toolbar for display commands.
        /// </summary>
        /// </field>
        /// <icon src="ui_winjs.ui.appbar.12x12.png" width="12" height="12" />
        /// <icon src="ui_winjs.ui.appbar.16x16.png" width="16" height="16" />
        /// <htmlSnippet supportsContent="true"><![CDATA[<div data-win-control="WinJS.UI.AppBar">
        /// <button data-win-control="WinJS.UI.AppBarCommand" data-win-options="{id:'',label:'example',icon:'back',type:'button',onclick:null,section:'global'}"></button>
        /// </div>]]></htmlSnippet>
        /// <event name="beforeshow" locid="WinJS.UI.AppBar_e:beforeshow">Raised just before showing the AppBar.</event>
        /// <event name="aftershow" locid="WinJS.UI.AppBar_e:aftershow">Raised immediately after the AppBar is fully shown.</event>
        /// <event name="beforehide" locid="WinJS.UI.AppBar_e:beforehide">Raised just before hiding the AppBar.</event>
        /// <event name="afterhide" locid="WinJS.UI.AppBar_e:afterhide">Raised immediately after the AppBar is fully hidden.</event>
        /// <part name="appbar" class="win-commandlayout" locid="WinJS.UI.AppBar_part:appbar">The AppBar control itself.</part>
        /// <part name="appBarCustom" class="win-appbar" locid="WinJS.UI.AppBar_part:appBarCustom">Style for a custom layout AppBar.</part>
        /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
        /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
        /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
        AppBar: _Base.Namespace._lazy(function () {
            var Key = _ElementUtilities.Key;

            // Enum of known constant pixel values for display modes.
            var knownVisibleHeights = {
                disabled: 0,
                none: 0,
                hidden: 0,
                minimal: 25,
            };

            // Maps each notion of a display modes to the corresponding visible position
            var displayModeVisiblePositions = {
                disabled: "hidden",
                none: "hidden",
                hidden: "hidden",
                minimal: "minimal",
                shown: "shown",
            };

            // Enum of closedDisplayMode constants
            var closedDisplayModes = {
                none: "none",
                minimal: "minimal",
            };

            // Constants shown/hidden states
            var appbarShownState = "shown",
                appbarHiddenState = "hidden";

            // Hook into event
            var appBarCommandEvent = false;
            var edgyHappening = null;

            // Handler for the edgy starting/completed/cancelled events
            function _completedEdgy(e) {
                // If we had a right click on a flyout, ignore it.
                if (_Overlay._Overlay._rightMouseMightEdgy &&
                    e.kind === _WinRT.Windows.UI.Input.EdgeGestureKind.mouse) {
                    return;
                }
                if (edgyHappening) {
                    // Edgy was happening, just skip it
                    edgyHappening = null;
                } else {
                    // Edgy wasn't happening, so toggle
                    var keyboardInvoked = e.kind === _WinRT.Windows.UI.Input.EdgeGestureKind.keyboard;
                    AppBar._toggleAllAppBarsState(keyboardInvoked);
                }
            }

            function _startingEdgy() {
                if (!edgyHappening) {
                    // Edgy wasn't happening, so toggle & start it
                    edgyHappening = AppBar._toggleAllAppBarsState(false);
                }
            }

            function _canceledEdgy() {
                // Shouldn't get here unless edgy was happening.
                // Undo whatever we were doing.
                var bars = _getDynamicBarsForEdgy();
                if (edgyHappening === "showing") {
                    _Overlay._Overlay._hideAllBars(bars, false);
                } else if (edgyHappening === "hiding") {
                    _Overlay._Overlay._showAllBars(bars, false);
                }
                edgyHappening = null;
            }

            function _allManipulationChanged(event) {
                var elements = _Global.document.querySelectorAll("." + _Constants.appBarClass);
                if (elements) {
                    var len = elements.length;
                    for (var i = 0; i < len; i++) {
                        var element = elements[i];
                        var appbar = element.winControl;
                        if (appbar && !element.disabled) {
                            appbar._manipulationChanged(event);
                        }
                    }
                }
            }

            // Get all the non-sticky bars and return them.
            // Returns array of AppBar objects.
            // The array also has _hidden and/or _shown set if ANY are hidden or shown.
            function _getDynamicBarsForEdgy() {
                var elements = _Global.document.querySelectorAll("." + _Constants.appBarClass);
                var len = elements.length;
                var AppBars = [];
                AppBars._shown = false;
                AppBars._hidden = false;
                for (var i = 0; i < len; i++) {
                    var element = elements[i];
                    if (element.disabled) {
                        // Skip disabled AppBars
                        continue;
                    }
                    var AppBar = element.winControl;
                    if (AppBar) {
                        AppBars.push(AppBar);
                        if (_ElementUtilities.hasClass(AppBar._element, _Constants.hiddenClass) || _ElementUtilities.hasClass(AppBar._element, _Constants.hidingClass)) {
                            AppBars._hidden = true;
                        } else {
                            AppBars._shown = true;
                        }
                    }
                }

                return AppBars;
            }

            // Sets focus to the last AppBar in the provided appBars array with given placement.
            // Returns true if focus was set.  False otherwise.
            function _setFocusToPreviousAppBarHelper(startIndex, appBarPlacement, appBars) {
                var appBar;
                for (var i = startIndex; i >= 0; i--) {
                    appBar = appBars[i].winControl;
                    if (appBar
                     && appBar.placement === appBarPlacement
                     && !appBar.hidden
                     && appBar._focusOnLastFocusableElement
                     && appBar._focusOnLastFocusableElement()) {
                        return true;
                    }
                }
                return false;
            }

            // Sets focus to the last tab stop of the previous AppBar
            // AppBar tabbing order:
            //    1) Bottom AppBars
            //    2) Top AppBars
            // DOM order is respected, because an AppBar should not have a defined tabIndex
            function _setFocusToPreviousAppBar() {
                /*jshint validthis: true */
                var appBars = _Global.document.querySelectorAll("." + _Constants.appBarClass);
                if (!appBars.length) {
                    return;
                }

                var thisAppBarIndex = 0;
                for (var i = 0; i < appBars.length; i++) {
                    if (appBars[i] === this.parentElement) {
                        thisAppBarIndex = i;
                        break;
                    }
                }

                var appBarControl = this.parentElement.winControl;
                if (appBarControl.placement === _Constants.appBarPlacementBottom) {
                    // Bottom appBar: Focus order: (1)previous bottom appBars (2)top appBars (3)bottom appBars
                    if (thisAppBarIndex && _setFocusToPreviousAppBarHelper(thisAppBarIndex - 1, _Constants.appBarPlacementBottom, appBars)) { return; }
                    if (_setFocusToPreviousAppBarHelper(appBars.length - 1, _Constants.appBarPlacementTop, appBars)) { return; }
                    if (_setFocusToPreviousAppBarHelper(appBars.length - 1, _Constants.appBarPlacementBottom, appBars)) { return; }
                } else if (appBarControl.placement === _Constants.appBarPlacementTop) {
                    // Top appBar: Focus order: (1)previous top appBars (2)bottom appBars (3)top appBars
                    if (thisAppBarIndex && _setFocusToPreviousAppBarHelper(thisAppBarIndex - 1, _Constants.appBarPlacementTop, appBars)) { return; }
                    if (_setFocusToPreviousAppBarHelper(appBars.length - 1, _Constants.appBarPlacementBottom, appBars)) { return; }
                    if (_setFocusToPreviousAppBarHelper(appBars.length - 1, _Constants.appBarPlacementTop, appBars)) { return; }
                }
            }

            // Sets focus to the first AppBar in the provided appBars array with given placement.
            // Returns true if focus was set.  False otherwise.
            function _setFocusToNextAppBarHelper(startIndex, appBarPlacement, appBars) {
                var appBar;
                for (var i = startIndex; i < appBars.length; i++) {
                    appBar = appBars[i].winControl;
                    if (appBar
                     && appBar.placement === appBarPlacement
                     && !appBar.hidden
                     && appBar._focusOnFirstFocusableElement
                     && appBar._focusOnFirstFocusableElement()) {
                        return true;
                    }
                }
                return false;
            }

            // Sets focus to the first tab stop of the next AppBar
            // AppBar tabbing order:
            //    1) Bottom AppBars
            //    2) Top AppBars
            // DOM order is respected, because an AppBar should not have a defined tabIndex
            function _setFocusToNextAppBar() {
                /*jshint validthis: true */
                var appBars = _Global.document.querySelectorAll("." + _Constants.appBarClass);

                var thisAppBarIndex = 0;
                for (var i = 0; i < appBars.length; i++) {
                    if (appBars[i] === this.parentElement) {
                        thisAppBarIndex = i;
                        break;
                    }
                }

                if (this.parentElement.winControl.placement === _Constants.appBarPlacementBottom) {
                    // Bottom appBar: Focus order: (1)next bottom appBars (2)top appBars (3)bottom appBars
                    if (_setFocusToNextAppBarHelper(thisAppBarIndex + 1, _Constants.appBarPlacementBottom, appBars)) { return; }
                    if (_setFocusToNextAppBarHelper(0, _Constants.appBarPlacementTop, appBars)) { return; }
                    if (_setFocusToNextAppBarHelper(0, _Constants.appBarPlacementBottom, appBars)) { return; }
                } else if (this.parentElement.winControl.placement === _Constants.appBarPlacementTop) {
                    // Top appBar: Focus order: (1)next top appBars (2)bottom appBars (3)top appBars
                    if (_setFocusToNextAppBarHelper(thisAppBarIndex + 1, _Constants.appBarPlacementTop, appBars)) { return; }
                    if (_setFocusToNextAppBarHelper(0, _Constants.appBarPlacementBottom, appBars)) { return; }
                    if (_setFocusToNextAppBarHelper(0, _Constants.appBarPlacementTop, appBars)) { return; }
                }
            }

            // Updates the firstDiv & finalDiv of all shown AppBars
            function _updateAllAppBarsFirstAndFinalDiv() {
                var appBars = _Global.document.querySelectorAll("." + _Constants.appBarClass);
                var appBar;
                for (var i = 0; i < appBars.length; i++) {
                    appBar = appBars[i].winControl;
                    if (appBar
                     && !appBar.hidden
                     && appBar._updateFirstAndFinalDiv) {
                        appBar._updateFirstAndFinalDiv();
                    }
                }
            }

            // Returns true if a visible non-sticky (light dismiss) AppBar is found in the document
            function _isThereVisibleNonStickyBar() {
                var appBars = _Global.document.querySelectorAll("." + _Constants.appBarClass);
                for (var i = 0; i < appBars.length; i++) {
                    var appBarControl = appBars[i].winControl;
                    if (appBarControl && !appBarControl.sticky &&
                        (!appBarControl.hidden || appBarControl._element.winAnimating === displayModeVisiblePositions.shown)) {
                        return true;
                    }
                }
                return false;
            }

            // If the previous focus was not a AppBar or CED, store it in the cache
            // (_isAppBarOrChild tests CED for us).
            function _checkStorePreviousFocus(focusEvent) {
                if (focusEvent.relatedTarget
                 && focusEvent.relatedTarget.focus
             && !_Overlay._Overlay._isAppBarOrChild(focusEvent.relatedTarget)) {
                    _storePreviousFocus(focusEvent.relatedTarget);
                }
            }

            // Cache the previous focus information
            function _storePreviousFocus(element) {
                if (element) {
                    _Overlay._Overlay._ElementWithFocusPreviousToAppBar = element;
                }
            }

            // Try to return focus to what had focus before.
            // If successfully return focus to a textbox, restore the selection too.
            function _restorePreviousFocus() {
                _Overlay._Overlay._trySetActive(_Overlay._Overlay._ElementWithFocusPreviousToAppBar);
            }

            var strings = {
                get ariaLabel() { return _Resources._getWinJSString("ui/appBarAriaLabel").value; },
                get requiresCommands() { return "Invalid argument: commands must not be empty"; },
                get cannotChangePlacementWhenVisible() { return "Invalid argument: The placement property cannot be set when the AppBar is visible, call hide() first"; },
                get badLayout() { return "Invalid argument: The layout property must be 'custom' or 'commands'"; },
                get cannotChangeLayoutWhenVisible() { return "Invalid argument: The layout property cannot be set when the AppBar is visible, call hide() first"; }
            };

            var AppBar = _Base.Class.derive(_Overlay._Overlay, function AppBar_ctor(element, options) {
                /// <signature helpKeyword="WinJS.UI.AppBar.AppBar">
                /// <summary locid="WinJS.UI.AppBar.constructor">
                /// Creates a new AppBar control.
                /// </summary>
                /// <param name="element" type="HTMLElement" domElement="true" locid="WinJS.UI.AppBar.constructor_p:element">
                /// The DOM element that will host the control.
                /// </param>
                /// <param name="options" type="Object" locid="WinJS.UI.AppBar.constructor_p:options">
                /// The set of properties and values to apply to the new AppBar control.
                /// </param>
                /// <returns type="WinJS.UI.AppBar" locid="WinJS.UI.AppBar.constructor_returnValue">
                /// The new AppBar control.
                /// </returns>
                /// </signature>

                this._initializing = true;

                // Simplify checking later
                options = options || {};

                // Make sure there's an element
                this._element = element || _Global.document.createElement("div");
                this._id = this._element.id || _ElementUtilities._uniqueID(this._element);
                this._writeProfilerMark("constructor,StartTM");

                if (!this._element.hasAttribute("tabIndex")) {
                    this._element.tabIndex = -1;
                }

                // Attach our css class.
                _ElementUtilities.addClass(this._element, _Constants.appBarClass);

                // Make sure we have an ARIA role
                var role = this._element.getAttribute("role");
                if (!role) {
                    this._element.setAttribute("role", "menubar");
                }
                var label = this._element.getAttribute("aria-label");
                if (!label) {
                    this._element.setAttribute("aria-label", strings.ariaLabel);
                }

                // Call the _Overlay constructor helper to finish setting up our element.
                // Don't pass constructor options, AppBar needs to set those itself specific order.
                this._baseOverlayConstructor(this._element);

                // Start off hidden
                this._lastPositionVisited = displayModeVisiblePositions.none;
                _ElementUtilities.addClass(this._element, _Constants.hiddenClass);

                // validate that if they didn't set commands, but want command
                // layout that the HTML only contains commands.  Do this first
                // so that we don't leave partial AppBars in the DOM.
                if (options.layout !== _Constants.appBarLayoutCustom && !options.commands && this._element) {
                    // Shallow copy object so we can modify it.
                    options = _BaseUtils._shallowCopy(options);
                    options.commands = this._verifyCommandsOnly(this._element, "WinJS.UI.AppBarCommand");
                }

                // Add Invoke button.
                this._invokeButton = _Global.document.createElement("button");
                this._invokeButton.tabIndex = 0;
                this._invokeButton.innerHTML = "<span class='" + _Constants.ellipsisClass + "'></span>";
                _ElementUtilities.addClass(this._invokeButton, _Constants.invokeButtonClass);
                this._element.appendChild(this._invokeButton);
                var that = this;
                _ElementUtilities._addEventListener(this._invokeButton, "pointerdown", function () { _Overlay._Overlay._addHideFocusClass(that._invokeButton); }, false);
                this._invokeButton.addEventListener("click", function () { AppBar._toggleAllAppBarsState(_KeyboardBehavior._keyboardSeenLast, that); }, false);

                // Run layout setter immediately. We need to know our layout in order to correctly
                // position any commands that may be getting set through the constructor.
                this.layout = options.layout || _Constants.appBarLayoutCommands;
                delete options.layout;

                // Need to set placement before closedDisplayMode, closedDisplayMode sets our starting position, which is dependant on placement.
                this.placement = options.placement || _Constants.appBarPlacementBottom;
                this.closedDisplayMode = options.closedDisplayMode || closedDisplayModes.minimal;

                _Control.setOptions(this, options);

                this._initializing = false;

                // Make a click eating div
                _Overlay._Overlay._createClickEatingDivAppBar();

                // Handle key down (esc) and (left & right)
                this._element.addEventListener("keydown", this._handleKeyDown.bind(this), false);

                // Attach event handler
                if (!appBarCommandEvent) {
                    // We'll trigger on invoking.  Could also have invoked or canceled
                    // Eventually we may want click up on invoking and drop back on invoked.
                    // Check for namespace so it'll behave in the designer.
                    if (_WinRT.Windows.UI.Input.EdgeGesture) {
                        var commandUI = _WinRT.Windows.UI.Input.EdgeGesture.getForCurrentView();
                        commandUI.addEventListener("starting", _startingEdgy);
                        commandUI.addEventListener("completed", _completedEdgy);
                        commandUI.addEventListener("canceled", _canceledEdgy);
                    }

                    // Need to know if the IHM is done scrolling
                    _Global.document.addEventListener("MSManipulationStateChanged", _allManipulationChanged, false);

                    appBarCommandEvent = true;
                }

                // Make sure _Overlay event handlers are hooked up (this aids light dismiss)
                this._addOverlayEventHandlers(false);

                // Need to store what had focus before
                _ElementUtilities._addEventListener(this._element, "focusin", function (event) { _checkStorePreviousFocus(event); }, false);

                // Need to hide ourselves if we lose focus
                _ElementUtilities._addEventListener(this._element, "focusout", function () { _Overlay._Overlay._hideIfAllAppBarsLostFocus(); }, false);


                if (this.closedDisplayMode === closedDisplayModes.none && this.layout === _Constants.appBarLayoutCommands) {
                    // Remove the commands layout AppBar from the layout tree at this point so we don't cause unnecessary layout costs whenever
                    // the window resizes or when CSS changes are applied to the commands layout AppBar's parent element.
                    this._element.style.display = "none";
                }

                this._writeProfilerMark("constructor,StopTM");

                return this;
            }, {
                // Public Properties

                /// <field type="String" defaultValue="bottom" oamOptionsDatatype="WinJS.UI.AppBar.placement" locid="WinJS.UI.AppBar.placement" helpKeyword="WinJS.UI.AppBar.placement">The placement of the AppBar on the display.  Values are "top" or "bottom".</field>
                placement: {
                    get: function AppBar_get_placement() {
                        return this._placement;
                    },
                    set: function AppBar_set_placement(value) {
                        // In designer we may have to move it
                        var wasShown = false;
                        if (_WinRT.Windows.ApplicationModel.DesignMode.designModeEnabled) {
                            this._hide();
                            wasShown = true;
                        }

                        if (!this.hidden) {
                            throw new _ErrorFromName("WinJS.UI.AppBar.CannotChangePlacementWhenVisible", strings.cannotChangePlacementWhenVisible);
                        }

                        // Set placement, coerce invalid values to 'bottom'
                        this._placement = (value === _Constants.appBarPlacementTop) ? _Constants.appBarPlacementTop : _Constants.appBarPlacementBottom;

                        // Clean up win-top, win-bottom styles
                        if (this._placement === _Constants.appBarPlacementTop) {
                            _ElementUtilities.addClass(this._element, _Constants.topClass);
                            _ElementUtilities.removeClass(this._element, _Constants.bottomClass);
                        } else if (this._placement === _Constants.appBarPlacementBottom) {
                            _ElementUtilities.removeClass(this._element, _Constants.topClass);
                            _ElementUtilities.addClass(this._element, _Constants.bottomClass);
                        }

                        // Show again if we hid ourselves for the designer
                        if (wasShown) {
                            this._show();
                        }
                    }
                },

                /// <field type="String" defaultValue="commands" oamOptionsDatatype="WinJS.UI.AppBar.layout" locid="WinJS.UI.AppBar.layout" helpKeyword="WinJS.UI.AppBar.layout">
                /// Gets or sets the layout of the AppBar contents to either "commands" or "custom".
                /// </field>
                layout: {
                    get: function AppBar_get_layout() {
                        return this._layout.type;
                    },
                    set: function (layout) {
                        if (layout !== _Constants.appBarLayoutCommands && layout !== _Constants.appBarLayoutCustom) {
                            throw new _ErrorFromName("WinJS.UI.AppBar.BadLayout", strings.badLayout);
                        }

                        // In designer we may have to redraw it
                        var wasShown = false;
                        if (_WinRT.Windows.ApplicationModel.DesignMode.designModeEnabled) {
                            this._hide();
                            wasShown = true;
                        }

                        if (!this.hidden) {
                            throw new _ErrorFromName("WinJS.UI.AppBar.CannotChangeLayoutWhenVisible", strings.cannotChangeLayoutWhenVisible);
                        }

                        var commands;
                        if (!this._initializing) {
                            // Gather commands in preparation for hand off to new layout.
                            // We expect prev layout to return commands in the order they were set in,
                            // not necessarily the current DOM order the layout is using.
                            commands = this._layout.commandsInOrder;
                            this._layout.disconnect();
                        }

                        // Set layout
                        if (layout === _Constants.appBarLayoutCommands) {
                            this._layout = new _Layouts._AppBarCommandsLayout();
                        } else {
                            // Custom layout uses Base AppBar Layout class.
                            this._layout = new _Layouts._AppBarBaseLayout();
                        }
                        this._layout.connect(this._element);

                        if (commands && commands.length) {
                            // Reset AppBar since layout changed.
                            this._layoutCommands(commands);
                        }
                        this._layout.connect(this._element);

                        if (commands && commands.length) {
                            // Reset AppBar since layout changed.
                            this._layoutCommands(commands);
                        }

                        // Show again if we hid ourselves for the designer
                        if (wasShown) {
                            this._show();
                        }
                    },
                    configurable: true
                },

                /// <field type="Boolean" locid="WinJS.UI.AppBar.sticky" isAdvanced="true" helpKeyword="WinJS.UI.AppBar.sticky">
                /// Gets or sets value that indicates whether the AppBar is sticky.
                /// This value is true if the AppBar is sticky; otherwise, it's false.
                /// </field>
                sticky: {
                    get: function AppBar_get_sticky() {
                        return this._sticky;
                    },
                    set: function AppBar_set_sticky(value) {
                        // If it doesn't change, do nothing
                        if (this._sticky === !!value) {
                            return;
                        }

                        this._sticky = !!value;

                        // Note: caller still has to call .show() if also want it shown.

                        // Show or hide the click eating div based on sticky value
                        if (!this.hidden && this._element.style.visibility === "visible") {
                            // May have changed sticky state for keyboard navigation
                            _updateAllAppBarsFirstAndFinalDiv();

                            // Ensure that the click eating div is in the correct state
                            if (this._sticky) {
                                if (!_isThereVisibleNonStickyBar()) {
                                    _Overlay._Overlay._hideClickEatingDivAppBar();
                                }
                            } else {
                                _Overlay._Overlay._showClickEatingDivAppBar();

                                if (this._shouldStealFocus()) {
                                    _storePreviousFocus(_Global.document.activeElement);
                                    this._setFocusToAppBar();
                                }
                            }
                        }
                    }
                },

                /// <field type="Array" locid="WinJS.UI.AppBar.commands" helpKeyword="WinJS.UI.AppBar.commands" isAdvanced="true">
                /// Sets the AppBarCommands in the AppBar. This property accepts an array of AppBarCommand objects.
                /// </field>
                commands: {
                    set: function AppBar_set_commands(commands) {
                        // Fail if trying to set when shown
                        if (!this.hidden) {
                            throw new _ErrorFromName("WinJS.UI.AppBar.CannotChangeCommandsWhenVisible", _Resources._formatString(_Overlay._Overlay.commonstrings.cannotChangeCommandsWhenVisible, "AppBar"));
                        }

                        // Dispose old commands before tossing them out.
                        if (!this._initializing) {
                            // AppBarCommands defined in markup don't want to be disposed during initialization.
                            this._disposeChildren();
                        }
                        this._layoutCommands(commands);
                    }
                },

                _layoutCommands: function AppBar_layoutCommands(commands) {
                    // Function precondition: AppBar must not be shown.

                    // Empties AppBar HTML and repopulates with passed in commands.
                    _ElementUtilities.empty(this._element);
                    this._element.appendChild(this._invokeButton); // Keep our Show/Hide button.

                    // In case they had only one command to set...
                    if (!Array.isArray(commands)) {
                        commands = [commands];
                    }

                    this._layout.layout(commands);
                },

                /// <field type="String" defaultValue="minimal" locid="WinJS.UI.AppBar.closedDisplayMode" helpKeyword="WinJS.UI.AppBar.closedDisplayMode" isAdvanced="true">
                /// Gets/Sets how AppBar will display itself while hidden. Values are "none" and "minimal".
                /// </field>
                closedDisplayMode: {
                    get: function AppBar_get_closedDisplayMode() {
                        return this._closedDisplayMode;
                    },
                    set: function AppBar_set_closedDisplayMode(value) {
                        var oldValue = this._closedDisplayMode;

                        if (oldValue !== value) {
                            if (value === closedDisplayModes.none) {
                                this._closedDisplayMode = closedDisplayModes.none;
                                _ElementUtilities.removeClass(this._element, _Constants.minimalClass);
                            } else {
                                // Minimal is default fallback.
                                this._closedDisplayMode = closedDisplayModes.minimal;
                                _ElementUtilities.addClass(this._element, _Constants.minimalClass);
                            }

                            // The invoke button has changed the amount of available space in the AppBar. Layout might need to scale.
                            this._layout.resize();

                            if (_ElementUtilities.hasClass(this._element, _Constants.hiddenClass) || _ElementUtilities.hasClass(this._element, _Constants.hidingClass)) {
                                // If the value is being set while we are not showing, change to our new position.
                                this._changeVisiblePosition(displayModeVisiblePositions[this._closedDisplayMode]);
                            }
                        }
                    },
                },

                /// <field type="Boolean" locid="WinJS.UI.AppBar.disabled" helpKeyword="WinJS.UI.AppBar.disabled">
                /// Disable an AppBar, setting or getting the HTML disabled attribute. While disabled, the AppBar is hidden completely, and will not respond to attempts to show it.
                /// </field>
                disabled: {
                    get: function () {
                        // Ensure it's a boolean because we're using the DOM element to keep in-sync
                        return !!this._element.disabled;
                    },
                    set: function (disable) {
                        var disable = !!disable;
                        if (this.disabled !== disable) {
                            this._element.disabled = disable;
                            var toPosition;
                            if (disable) {
                                // Disabling. Move to the position mapped to the disabled state.
                                toPosition = displayModeVisiblePositions.disabled;
                            } else {
                                // Enabling. Move to the position mapped to our closedDisplayMode.
                                toPosition = displayModeVisiblePositions[this.closedDisplayMode];
                            }
                            this._hide(toPosition);
                        }
                    },
                },

                /// <field type="Boolean" hidden="true" locid="WinJS.UI._AppBar.hidden" helpKeyword="WinJS.UI._AppBar.hidden">Read only, true if an AppBar is 'hidden'.</field>
                hidden: {
                    get: function () {
                        // Returns true if AppBar is 'hidden'.
                        return _ElementUtilities.hasClass(this._element, _Constants.hiddenClass) ||
                            _ElementUtilities.hasClass(this._element, _Constants.hidingClass) ||
                            this._doNext === displayModeVisiblePositions.minimal ||
                            this._doNext === displayModeVisiblePositions.none;
                    },
                },

                getCommandById: function (id) {
                    /// <signature helpKeyword="WinJS.UI.AppBar.getCommandById">
                    /// <summary locid="WinJS.UI.AppBar.getCommandById">
                    /// Retrieves the command with the specified ID from this AppBar.
                    /// If more than one command is found, this method returns them all.
                    /// </summary>
                    /// <param name="id" type="String" locid="WinJS.UI.AppBar.getCommandById_p:id">Id of the command to return.</param>
                    /// <returns type="object" locid="WinJS.UI.AppBar.getCommandById_returnValue">
                    /// The command found, an array of commands if more than one have the same ID, or null if no command is found.
                    /// </returns>
                    /// </signature>
                    var commands = this.element.querySelectorAll("#" + id);
                    var newCommands = [];
                    for (var count = 0, len = commands.length; count < len; count++) {
                        if (commands[count].winControl) {
                            newCommands.push(commands[count].winControl);
                        }
                    }

                    if (newCommands.length === 1) {
                        return newCommands[0];
                    } else if (newCommands.length === 0) {
                        return null;
                    }

                    return newCommands;
                },

                showCommands: function (commands) {
                    /// <signature helpKeyword="WinJS.UI.AppBar.showCommands">
                    /// <summary locid="WinJS.UI.AppBar.showCommands">
                    /// Show the specified commands of the AppBar.
                    /// </summary>
                    /// <param name="commands" type="Array" locid="WinJS.UI.AppBar.showCommands_p:commands">
                    /// An array of the commands to show. The array elements may be AppBarCommand objects, or the string identifiers (IDs) of commands.
                    /// </param>
                    /// </signature>
                    if (!commands) {
                        throw new _ErrorFromName("WinJS.UI.AppBar.RequiresCommands", strings.requiresCommands);
                    }

                    this._showCommands(commands);
                },

                hideCommands: function (commands) {
                    /// <signature helpKeyword="WinJS.UI.AppBar.hideCommands">
                    /// <summary locid="WinJS.UI.AppBar.hideCommands">
                    /// Hides the specified commands of the AppBar.
                    /// </summary>
                    /// <param name="commands" type="Array" locid="WinJS.UI.AppBar.hideCommands_p:commands">Required. Command or Commands to hide, either String, DOM elements, or WinJS objects.</param>
                    /// </signature>
                    if (!commands) {
                        throw new _ErrorFromName("WinJS.UI.AppBar.RequiresCommands", strings.requiresCommands);
                    }

                    this._hideCommands(commands);
                },

                showOnlyCommands: function (commands) {
                    /// <signature helpKeyword="WinJS.UI.AppBar.showOnlyCommands">
                    /// <summary locid="WinJS.UI.AppBar.showOnlyCommands">
                    /// Show the specified commands, hiding all of the others in the AppBar.
                    /// </summary>
                    /// <param name="commands" type="Array" locid="WinJS.UI.AppBar.showOnlyCommands_p:commands">
                    /// An array of the commands to show. The array elements may be AppBarCommand objects, or the string identifiers (IDs) of commands.
                    /// </param>
                    /// </signature>
                    if (!commands) {
                        throw new _ErrorFromName("WinJS.UI.AppBar.RequiresCommands", strings.requiresCommands);
                    }

                    this._showOnlyCommands(commands);
                },

                show: function () {
                    /// <signature helpKeyword="WinJS.UI.AppBar.show">
                    /// <summary locid="WinJS.UI.AppBar.show">
                    /// Shows the AppBar, if hidden and not disabled, regardless of other state.
                    /// </summary>
                    /// </signature>
                    // Just wrap the private one, turning off keyboard invoked flag
                    this._writeProfilerMark("show,StartTM");
                    this._keyboardInvoked = false;
                    this._doNotFocus = !!this.sticky;
                    this._show();
                },

                _show: function AppBar_show() {

                    var toPosition = displayModeVisiblePositions.shown;
                    var showing = null;

                    // If we're already shown, we are just going to animate our position, not fire events or manage focus.
                    if (!this.disabled && (_ElementUtilities.hasClass(this._element, _Constants.hiddenClass) || _ElementUtilities.hasClass(this._element, _Constants.hidingClass))) {
                        showing = appbarShownState;
                    }

                    this._changeVisiblePosition(toPosition, showing);

                    if (showing) {
                        // Configure shown state for lightdismiss & sticky appbars.
                        if (!this.sticky) {
                            // Need click-eating div to be visible ASAP.
                            _Overlay._Overlay._showClickEatingDivAppBar();
                        }

                        // Clean up tabbing behavior by making sure first and final divs are correct after showing.
                        if (!this.sticky && _isThereVisibleNonStickyBar()) {
                            _updateAllAppBarsFirstAndFinalDiv();
                        } else {
                            this._updateFirstAndFinalDiv();
                        }

                        // Check if we should steal focus
                        if (!this._doNotFocus && this._shouldStealFocus()) {
                            // Store what had focus if nothing currently is stored
                            if (!_Overlay._Overlay._ElementWithFocusPreviousToAppBar) {
                                _storePreviousFocus(_Global.document.activeElement);
                            }

                            this._setFocusToAppBar();
                        }
                    }
                },

                hide: function () {
                    /// <signature helpKeyword="WinJS.UI.AppBar.hide">
                    /// <summary locid="WinJS.UI.AppBar.hide">
                    /// Hides the AppBar.
                    /// </summary>
                    /// </signature>
                    // Just wrap the private one
                    this._writeProfilerMark("hide,StartTM");
                    this._hide();
                },

                _hide: function AppBar_hide(toPosition) {

                    var toPosition = toPosition || displayModeVisiblePositions[this.closedDisplayMode];
                    var hiding = null;

                    // If were already hidden, we are just going to animate our position, not fire events or manage focus again.
                    if (!_ElementUtilities.hasClass(this._element, _Constants.hiddenClass) && !_ElementUtilities.hasClass(this._element, _Constants.hidingClass)) {
                        hiding = appbarHiddenState;
                    }

                    this._changeVisiblePosition(toPosition, hiding);
                    if (hiding) {
                        // Determine if there are any AppBars that are shown.
                        // Set the focus to the next shown AppBar.
                        // If there are none, set the focus to the control stored in the cache, which
                        //   is what had focus before the AppBars were given focus.
                        var appBars = _Global.document.querySelectorAll("." + _Constants.appBarClass);
                        var areOtherAppBars = false;
                        var areOtherNonStickyAppBars = false;
                        var i;
                        for (i = 0; i < appBars.length; i++) {
                            var appBarControl = appBars[i].winControl;
                            if (appBarControl && !appBarControl.hidden && (appBarControl !== this)) {
                                areOtherAppBars = true;

                                if (!appBarControl.sticky) {
                                    areOtherNonStickyAppBars = true;
                                    break;
                                }
                            }
                        }

                        var settingsFlyouts = _Global.document.querySelectorAll("." + _Constants.settingsFlyoutClass);
                        var areVisibleSettingsFlyouts = false;
                        for (i = 0; i < settingsFlyouts.length; i++) {
                            var settingsFlyoutControl = settingsFlyouts[i].winControl;
                            if (settingsFlyoutControl && !settingsFlyoutControl.hidden) {
                                areVisibleSettingsFlyouts = true;
                                break;
                            }
                        }

                        if (!areOtherNonStickyAppBars && !areVisibleSettingsFlyouts) {
                            // Hide the click eating div because there are no other AppBars showing
                            _Overlay._Overlay._hideClickEatingDivAppBar();
                        }

                        var that = this;
                        if (!areOtherAppBars) {
                            // Set focus to what had focus before showing the AppBar
                            if (_Overlay._Overlay._ElementWithFocusPreviousToAppBar &&
                                (!_Global.document.activeElement || _Overlay._Overlay._isAppBarOrChild(_Global.document.activeElement))) {
                                _restorePreviousFocus();
                            }
                            // Always clear the previous focus (to prevent temporary leaking of element)
                            _Overlay._Overlay._ElementWithFocusPreviousToAppBar = null;
                        } else if (AppBar._isWithinAppBarOrChild(_Global.document.activeElement, that.element)) {
                            // Set focus to next visible AppBar in DOM

                            var foundCurrentAppBar = false;
                            for (i = 0; i <= appBars.length; i++) {
                                if (i === appBars.length) {
                                    i = 0;
                                }

                                var appBar = appBars[i];
                                if (appBar === this.element) {
                                    foundCurrentAppBar = true;
                                } else if (foundCurrentAppBar && !appBar.winControl.hidden) {
                                    appBar.winControl._keyboardInvoked = !!this._keyboardInvoked;
                                    appBar.winControl._setFocusToAppBar();
                                    break;
                                }
                            }
                        }

                        // If we are hiding the last lightDismiss AppBar,
                        //   then we need to update the tabStops of the other AppBars
                        if (!this.sticky && !_isThereVisibleNonStickyBar()) {
                            _updateAllAppBarsFirstAndFinalDiv();
                        }

                        // Reset these values
                        this._keyboardInvoked = false;
                        this._doNotFocus = false;
                    }
                },

                _dispose: function AppBar_dispose() {
                    _Dispose.disposeSubTree(this.element);
                    this._layout.dispose();
                    this.disabled = true;

                },

                _disposeChildren: function AppBar_disposeChildren() {
                    // Be purposeful about what we dispose.
                    this._layout.disposeChildren();
                },

                _handleKeyDown: function AppBar_handleKeyDown(event) {
                    // On Left/Right arrow keys, moves focus to previous/next AppbarCommand element.
                    // On "Esc" key press hide flyouts and hide light dismiss AppBars.

                    // Esc hides light-dismiss AppBars in all layouts but if the user has a text box with an IME
                    // candidate window open, we want to skip the ESC key event since it is handled by the IME.
                    // When the IME handles a key it sets event.keyCode === Key.IME for an easy check.
                    if (event.keyCode === Key.escape && event.keyCode !== Key.IME) {
                        event.preventDefault();
                        event.stopPropagation();
                        _Overlay._Overlay._hideAllFlyouts();
                        _Overlay._Overlay._hideLightDismissAppBars(null, true);
                    }

                    // If the current active element isn't an intrinsic part of the AppBar,
                    // Layout might want to handle additional keys.
                    if (!this._invokeButton.contains(_Global.document.activeElement)) {
                        this._layout.handleKeyDown(event);
                    }
                },

                _visiblePixels: {
                    get: function () {
                        // Returns object containing pixel height of each visible position
                        return {
                            hidden: knownVisibleHeights.hidden,
                            minimal: knownVisibleHeights.minimal,
                            // Element can change size as content gets added or removed or if it
                            // experinces style changes. We have to look this up at run time.
                            shown: this._element.offsetHeight,
                        };
                    }
                },

                _visiblePosition: {
                    // Returns string value of our nearest, stationary, visible position.
                    get: function () {
                        // If we're animating into a new posistion, return the position we're animating into.
                        if (this._animating && displayModeVisiblePositions[this._element.winAnimating]) {
                            return this._element.winAnimating;
                        } else {
                            return this._lastPositionVisited;
                        }
                    }
                },

                _visible: {
                    // Returns true if our visible position is not completely hidden, else false.
                    get: function () {
                        return (this._visiblePosition !== displayModeVisiblePositions.none);
                    }
                },

                _changeVisiblePosition: function (toPosition, newState) {
                    /// <signature helpKeyword="WinJS.UI.AppBar._changeVisiblePosition">
                    /// <summary locid="WinJS.UI.AppBar._changeVisiblePosition">
                    /// Changes the visible position of the AppBar.
                    /// </summary>
                    /// <param name="toPosition" type="String" locid="WinJS.UI.AppBar._changeVisiblePosition_p:toPosition">
                    /// Name of the visible position we want to move to.
                    /// </param>
                    /// <param name="newState" type="String" locid="WinJS.UI.AppBar._changeVisiblePosition_p:newState">
                    /// Name of the state we are entering. Values can be "showing", "hiding" or null.
                    /// If the value is null, then we are not changing states, only changing visible position.
                    /// </param>
                    /// </signature>

                    if ((this._visiblePosition === toPosition && !this._keyboardObscured) ||
                        (this.disabled && toPosition !== displayModeVisiblePositions.disabled)) {
                        // If we want to go where we already are, or we're disabled, return false.
                        this._afterPositionChange(null);
                    } else if (this._animating || this._needToHandleShowingKeyboard || this._needToHandleHidingKeyboard) {
                        // Only do one thing at a time. If we are already animating,
                        // or the IHM is animating, schedule this for later.
                        this._doNext = toPosition;
                        this._afterPositionChange(null);
                    } else {
                        // Begin position changing sequence.

                        // Set the animating flag to block any queued position changes until we're done.
                        this._element.winAnimating = toPosition;
                        var performAnimation = this._initializing ? false : true;

                        // Assume we are animating from the last position visited.
                        var fromPosition = this._lastPositionVisited;

                        // We'll need to measure our element to determine how far we need to animate.
                        // Make sure we have accurate dimensions.
                        this._element.style.display = "";

                        // Are we hiding completely, or about to become visible?
                        var hidingCompletely = (toPosition === displayModeVisiblePositions.hidden);

                        if (this._keyboardObscured) {
                            // We're changing position while covered by the IHM.
                            if (hidingCompletely) {
                                // If we're covered by the IHM we already look hidden.
                                // We can skip our animation and just hide.
                                performAnimation = false;
                            } else {
                                // Some portion of the AppBar should be visible to users after its position changes.

                                // Un-obscure ourselves and become visible to the user again.
                                // Need to animate to our desired position as if we were coming up from behind the keyboard.
                                fromPosition = displayModeVisiblePositions.hidden;
                            }
                            this._keyboardObscured = false;
                        }

                        // Fire "before" event if we are changing state.
                        if (newState === appbarShownState) {
                            this._beforeShow();
                        } else if (newState === appbarHiddenState) {
                            this._beforeHide();
                        }

                        // Position our element into the correct "end of animation" position,
                        // also accounting for any viewport scrolling or soft keyboard positioning.
                        this._ensurePosition();

                        this._element.style.opacity = 1;
                        this._element.style.visibility = "visible";

                        this._animationPromise = (performAnimation) ? this._animatePositionChange(fromPosition, toPosition) : Promise.wrap();
                        this._animationPromise.then(
                            function () { this._afterPositionChange(toPosition, newState); }.bind(this),
                            function () { this._afterPositionChange(toPosition, newState); }.bind(this)
                        );
                    }
                },

                _afterPositionChange: function AppBar_afterPosiitonChange(newPosition, newState) {
                    // Defines body of work to perform after changing positions.
                    if (this._disposed) {
                        return;
                    }

                    if (newPosition) {
                        // Clear animation flag and record having visited this position.
                        this._element.winAnimating = "";
                        this._lastPositionVisited = newPosition;

                        if (this._doNext === this._lastPositionVisited) {
                            this._doNext = "";
                        }

                        if (newPosition === displayModeVisiblePositions.hidden) {
                            // Make sure animation is finished.
                            this._element.style.visibility = "hidden";
                            this._element.style.display = "none";
                        }

                        // Clean up animation transforms.
                        var transformProperty = _BaseUtils._browserStyleEquivalents["transform"].scriptName;
                        this._element.style[transformProperty] = "";

                        // Fire "after" event if we changed state.
                        if (newState === appbarShownState) {
                            this._afterShow();
                        } else if (newState === appbarHiddenState) {
                            this._afterHide();
                        }

                        // If we had something queued, do that
                        Scheduler.schedule(this._checkDoNext, Scheduler.Priority.normal, this, "WinJS.UI.AppBar._checkDoNext");
                    }

                    this._afterPositionChangeCallBack();
                },

                _afterPositionChangeCallBack: function () {
                    // Leave this blank for unit tests to overwrite.
                },

                _beforeShow: function AppBar_beforeShow() {
                    // Each overlay tracks the size of the <HTML> element for triggering light-dismiss in the window resize handler.
                    this._cachedDocumentSize = this._cachedDocumentSize || _Overlay._Overlay._sizeOfDocument();

                    // In case their event 'beforeshow' event listener is going to manipulate commands,
                    // first see if there are any queued command animations we can handle while we're still hidden.
                    if (this._queuedCommandAnimation) {
                        this._showAndHideFast(this._queuedToShow, this._queuedToHide);
                        this._queuedToShow = [];
                        this._queuedToHide = [];
                    }

                    // Make sure everything fits before showinging
                    this._layout.scale();

                    _ElementUtilities.removeClass(this._element, _Constants.hiddenClass);
                    _ElementUtilities.addClass(this._element, _Constants.showingClass);

                    // Send our "beforeShow" event
                    this._sendEvent(_Overlay._Overlay.beforeShow);
                },

                _afterShow: function AppBar_afterShow() {
                    _ElementUtilities.removeClass(this._element, _Constants.showingClass);
                    _ElementUtilities.addClass(this._element, _Constants.shownClass);

                    // Send our "afterShow" event
                    this._sendEvent(_Overlay._Overlay.afterShow);
                    this._writeProfilerMark("show,StopTM");
                },

                _beforeHide: function AppBar_beforeHide() {

                    _ElementUtilities.removeClass(this._element, _Constants.shownClass);
                    _ElementUtilities.addClass(this._element, _Constants.hidingClass);

                    // Send our "beforeHide" event
                    this._sendEvent(_Overlay._Overlay.beforeHide);
                },

                _afterHide: function AppBar_afterHide() {

                    // In case their 'afterhide' event handler is going to manipulate commands,
                    // first see if there are any queued command animations we can handle now we're hidden.
                    if (this._queuedCommandAnimation) {
                        this._showAndHideFast(this._queuedToShow, this._queuedToHide);
                        this._queuedToShow = [];
                        this._queuedToHide = [];
                    }

                    _ElementUtilities.removeClass(this._element, _Constants.hidingClass);
                    _ElementUtilities.addClass(this._element, _Constants.hiddenClass);

                    // Send our "afterHide" event
                    this._sendEvent(_Overlay._Overlay.afterHide);
                    this._writeProfilerMark("hide,StopTM");
                },

                _animatePositionChange: function AppBar_animatePositionChange(fromPosition, toPosition) {
                    // Determines and executes the proper transition between visible positions

                    // Get values in terms of pixels to perform animation.
                    var beginningVisiblePixelHeight = this._visiblePixels[fromPosition],
                        endingVisiblePixelHeight = this._visiblePixels[toPosition],
                        distance = Math.abs(endingVisiblePixelHeight - beginningVisiblePixelHeight),
                        offsetTop = (this._placement === _Constants.appBarPlacementTop) ? -distance : distance;

                    // Animate
                    if (endingVisiblePixelHeight > beginningVisiblePixelHeight) {
                        var fromOffset = { top: offsetTop + "px", left: "0px" };
                        return Animations.showEdgeUI(this._element, fromOffset, { mechanism: "transition" });
                    } else {
                        var toOffset = { top: offsetTop + "px", left: "0px" };
                        return Animations.hideEdgeUI(this._element, toOffset, { mechanism: "transition" });
                    }
                },

                _checkDoNext: function AppBar_checkDoNext() {
                    // Do nothing if we're still animating
                    if (this._animating || this._needToHandleShowingKeyboard || this._needToHandleHidingKeyboard || this._disposed) {
                        return;
                    }

                    if (this._doNext === displayModeVisiblePositions.disabled ||
                        this._doNext === displayModeVisiblePositions.hidden ||
                        this._doNext === displayModeVisiblePositions.minimal) {
                        // Do hide first because animating commands would be easier
                        this._hide(this._doNext);
                        this._doNext = "";
                    } else if (this._queuedCommandAnimation) {
                        // Do queued commands before showing if possible
                        this._showAndHideQueue();
                    } else if (this._doNext === displayModeVisiblePositions.shown) {
                        // Show last so that we don't unnecessarily animate commands
                        this._show();
                        this._doNext = "";
                    }
                },

                _isABottomAppBarInTheProcessOfShowing: function AppBar_isABottomAppBarInTheProcessOfShowing() {
                    var appbars = _Global.document.querySelectorAll("." + _Constants.appBarClass + "." + _Constants.bottomClass);
                    for (var i = 0; i < appbars.length; i++) {
                        if (appbars[i].winAnimating === displayModeVisiblePositions.shown) {
                            return true;
                        }
                    }

                    return false;
                },

                // Returns true if
                //   1) This is a bottom appbar
                //   2) No appbar has focus and a bottom appbar is not in the process of showing
                //   3) What currently has focus is neither a bottom appbar nor a top appbar
                //      AND a bottom appbar is not in the process of showing.
                // Otherwise Returns false
                _shouldStealFocus: function AppBar_shouldStealFocus() {
                    var activeElementAppBar = _Overlay._Overlay._isAppBarOrChild(_Global.document.activeElement);
                    if (this._element === activeElementAppBar) {
                        // This appbar already has focus and we don't want to move focus
                        // from where it currently is in this appbar.
                        return false;
                    }
                    if (this._placement === _Constants.appBarPlacementBottom) {
                        // This is a bottom appbar
                        return true;
                    }

                    var isBottomAppBarShowing = this._isABottomAppBarInTheProcessOfShowing();
                    if (!activeElementAppBar) {
                        // Currently no appbar has focus.
                        // Return true if a bottom appbar is not in the process of showing.
                        return !isBottomAppBarShowing;
                    }
                    if (!activeElementAppBar.winControl) {
                        // This should not happen, but if it does we want to make sure
                        // that an AppBar ends up with focus.
                        return true;
                    }
                    if ((activeElementAppBar.winControl._placement !== _Constants.appBarPlacementBottom)
                     && (activeElementAppBar.winControl._placement !== _Constants.appBarPlacementTop)
                     && !isBottomAppBarShowing) {
                        // What currently has focus is neither a bottom appbar nor a top appbar
                        // -and-
                        // a bottom appbar is not in the process of showing.
                        return true;
                    }
                    return false;
                },

                // Set focus to the passed in AppBar
                _setFocusToAppBar: function AppBar_setFocusToAppBar() {
                    if (this._focusOnFirstFocusableElement()) {
                        // Prevent what is gaining focus from showing that it has focus,
                        // but only in the non-keyboard scenario.
                        if (!this._keyboardInvoked) {
                            _Overlay._Overlay._addHideFocusClass(_Global.document.activeElement);
                        }
                    } else {
                        // No first element, set it to appbar itself
                        _Overlay._Overlay._trySetActive(this._element);
                    }
                },

                _commandsUpdated: function AppBar_commandsUpdated() {
                    // If we are still initializing then we don't have a layout yet so it doesn't need updating.
                    if (!this._initializing) {
                        this._layout.commandsUpdated();
                        this._layout.scale();
                    }
                },

                _beginAnimateCommands: function AppBar_beginAnimateCommands(showCommands, hideCommands, otherVisibleCommands) {
                    // The parameters are 3 mutually exclusive arrays of win-command elements contained in this Overlay.
                    // 1) showCommands[]: All of the HIDDEN win-command elements that ARE scheduled to show.
                    // 2) hideCommands[]: All of the VISIBLE win-command elements that ARE scheduled to hide.
                    // 3) otherVisibleCommands[]: All VISIBLE win-command elements that ARE NOT scheduled to hide.
                    this._layout.beginAnimateCommands(showCommands, hideCommands, otherVisibleCommands);
                },

                _endAnimateCommands: function AppBar_endAnimateCommands() {
                    this._layout.endAnimateCommands();
                    this._endAnimateCommandsCallBack();
                },

                _endAnimateCommandsCallBack: function AppBar_endAnimateCommandsCallBack() {
                    // Leave this blank for unit tests to overwrite.
                },

                // Get the top offset for top appbars.
                _getTopOfVisualViewport: function AppBar_getTopOfVisualViewPort() {
                    return _Overlay._Overlay._keyboardInfo._visibleDocTop;
                },

                // Get the bottom offset for bottom appbars.
                _getAdjustedBottom: function AppBar_getAdjustedBottom() {
                    // Need the distance the IHM moved as well.
                    return _Overlay._Overlay._keyboardInfo._visibleDocBottomOffset;
                },

                _showingKeyboard: function AppBar_showingKeyboard(event) {
                    // Remember keyboard showing state.
                    this._keyboardObscured = false;
                    this._needToHandleHidingKeyboard = false;

                    // If we're already moved, then ignore the whole thing
                    if (_Overlay._Overlay._keyboardInfo._visible && this._alreadyInPlace()) {
                        return;
                    }

                    this._needToHandleShowingKeyboard = true;
                    // If focus is in the appbar, don't cause scrolling.
                    if (!this.hidden && this._element.contains(_Global.document.activeElement)) {
                        event.ensuredFocusedElementInView = true;
                    }

                    // Check if appbar moves or if we're ok leaving it obscured instead.
                    if (this._visible && this._placement !== _Constants.appBarPlacementTop && _Overlay._Overlay._isFlyoutVisible()) {
                        // Remember that we're obscured
                        this._keyboardObscured = true;
                    } else {
                        // Don't be obscured, clear _scrollHappened flag to give us inference later on when to re-show ourselves.
                        this._scrollHappened = false;
                    }

                    // Also set timeout regardless, so we can clean up our _keyboardShowing flag.
                    var that = this;
                    _Global.setTimeout(function (e) { that._checkKeyboardTimer(e); }, _Overlay._Overlay._keyboardInfo._animationShowLength + _Overlay._Overlay._scrollTimeout);
                },

                _hidingKeyboard: function AppBar_hidingKeyboard() {
                    // We'll either just reveal the current space under the IHM or restore the window height.

                    // We won't be obscured
                    this._keyboardObscured = false;
                    this._needToHandleShowingKeyboard = false;
                    this._needToHandleHidingKeyboard = true;

                    // We'll either just reveal the current space or resize the window
                    if (!_Overlay._Overlay._keyboardInfo._isResized) {
                        // If we're not completely hidden, only fake hiding under keyboard, or already animating,
                        // then snap us to our final position.
                        if (this._visible || this._animating) {
                            // Not resized, update our final position immediately
                            this._checkScrollPosition();
                            this._element.style.display = "";
                        }
                        this._needToHandleHidingKeyboard = false;
                    }
                    // Else resize should clear keyboardHiding.
                },

                _resize: function AppBar_resize(event) {
                    // If we're hidden by the keyboard, then hide bottom appbar so it doesn't pop up twice when it scrolls
                    if (this._needToHandleShowingKeyboard) {
                        // Top is allowed to scroll off the top, but we don't want bottom to peek up when
                        // scrolled into view since we'll show it ourselves and don't want a stutter effect.
                        if (this._visible) {
                            if (this._placement !== _Constants.appBarPlacementTop && !this._keyboardObscured) {
                                // If viewport doesn't match window, need to vanish momentarily so it doesn't scroll into view,
                                // however we don't want to toggle the visibility="hidden" hidden flag.
                                this._element.style.display = "none";
                            }
                        }
                        // else if we're top we stay, and if there's a flyout, stay obscured by the keyboard.
                    } else if (this._needToHandleHidingKeyboard) {
                        this._needToHandleHidingKeyboard = false;
                        if (this._visible || this._animating) {
                            // Snap to final position
                            this._checkScrollPosition();
                            this._element.style.display = "";
                        }
                    }

                    // Make sure everything still fits.
                    if (!this._initializing) {
                        this._layout.resize(event);
                    }
                },

                _checkKeyboardTimer: function AppBar_checkKeyboardTimer() {
                    if (!this._scrollHappened) {
                        this._mayEdgeBackIn();
                    }
                },

                _manipulationChanged: function AppBar_manipulationChanged(event) {
                    // See if we're at the not manipulating state, and we had a scroll happen,
                    // which is implicitly after the keyboard animated.
                    if (event.currentState === 0 && this._scrollHappened) {
                        this._mayEdgeBackIn();
                    }
                },

                _mayEdgeBackIn: function AppBar_mayEdgeBackIn() {
                    // May need to react to IHM being resized event
                    if (this._needToHandleShowingKeyboard) {
                        // If not top appbar or viewport isn't still at top, then need to show again
                        this._needToHandleShowingKeyboard = false;
                        // If obscured (IHM + flyout showing), it's ok to stay obscured.
                        // If bottom we have to move, or if top scrolled off screen.
                        if (!this._keyboardObscured &&
                            (this._placement !== _Constants.appBarPlacementTop || _Overlay._Overlay._keyboardInfo._visibleDocTop !== 0)) {
                            var toPosition = this._visiblePosition;
                            this._lastPositionVisited = displayModeVisiblePositions.hidden;
                            this._changeVisiblePosition(toPosition, false);
                        } else {
                            // Ensure any animations dropped during the showing keyboard are caught up.
                            this._checkDoNext();
                        }
                    }
                    this._scrollHappened = false;
                },

                _ensurePosition: function AppBar_ensurePosition() {
                    // Position the AppBar element relative to the top or bottom edge of the visible
                    // document, based on the the visible position we think we need to be in.
                    var offSet = this._computePositionOffset();
                    this._element.style.bottom = offSet.bottom;
                    this._element.style.top = offSet.top;

                },

                _computePositionOffset: function AppBar_computePositionOffset() {
                    // Calculates and returns top and bottom offsets for the AppBar element, relative to the top or bottom edge of the visible
                    // document.
                    var positionOffSet = {};

                    if (this._placement === _Constants.appBarPlacementBottom) {
                        // If the IHM is open, the bottom of the visual viewport may or may not be obscured
                        // Use _getAdjustedBottom to account for the IHM if it is covering the bottom edge.
                        positionOffSet.bottom = this._getAdjustedBottom() + "px";
                        positionOffSet.top = "";
                    } else if (this._placement === _Constants.appBarPlacementTop) {
                        positionOffSet.bottom = "";
                        positionOffSet.top = this._getTopOfVisualViewport() + "px";
                    }

                    return positionOffSet;
                },

                _checkScrollPosition: function AppBar_checkScrollPosition() {
                    // If IHM has appeared, then remember we may come in
                    if (this._needToHandleShowingKeyboard) {
                        // Tag that it's OK to edge back in.
                        this._scrollHappened = true;
                        return;
                    }

                    // We only need to update if we're not completely hidden.
                    if (this._visible || this._animating) {
                        this._ensurePosition();
                        // Ensure any animations dropped during the showing keyboard are caught up.
                        this._checkDoNext();
                    }
                },

                _alreadyInPlace: function AppBar_alreadyInPlace() {
                    // See if we're already where we're supposed to be.
                    var offSet = this._computePositionOffset();
                    return (offSet.top === this._element.style.top && offSet.bottom === this._element.style.bottom);
                },

                // If there is a shown non-sticky AppBar then it sets the firstDiv tabIndex to
                //   the minimum tabIndex found in the AppBars and finalDiv to the max found.
                // Otherwise sets their tabIndex to -1 so they are not tab stops.
                _updateFirstAndFinalDiv: function AppBar_updateFirstAndFinalDiv() {
                    var appBarFirstDiv = this._element.querySelectorAll("." + _Constants.firstDivClass);
                    appBarFirstDiv = appBarFirstDiv.length >= 1 ? appBarFirstDiv[0] : null;

                    var appBarFinalDiv = this._element.querySelectorAll("." + _Constants.finalDivClass);
                    appBarFinalDiv = appBarFinalDiv.length >= 1 ? appBarFinalDiv[0] : null;

                    // Remove the firstDiv & finalDiv if they are not at the appropriate locations
                    if (appBarFirstDiv && (this._element.children[0] !== appBarFirstDiv)) {
                        appBarFirstDiv.parentNode.removeChild(appBarFirstDiv);
                        appBarFirstDiv = null;
                    }
                    if (appBarFinalDiv && (this._element.children[this._element.children.length - 1] !== appBarFinalDiv)) {
                        appBarFinalDiv.parentNode.removeChild(appBarFinalDiv);
                        appBarFinalDiv = null;
                    }

                    // Create and add the firstDiv & finalDiv if they don't already exist
                    if (!appBarFirstDiv) {
                        // Add a firstDiv that will be the first child of the appBar.
                        // On focus set focus to the previous appBar.
                        // The div should only be focusable if there are shown non-sticky AppBars.
                        appBarFirstDiv = _Global.document.createElement("div");
                        // display: inline is needed so that the div doesn't take up space and cause the page to scroll on focus
                        appBarFirstDiv.style.display = "inline";
                        appBarFirstDiv.className = _Constants.firstDivClass;
                        appBarFirstDiv.tabIndex = -1;
                        appBarFirstDiv.setAttribute("aria-hidden", "true");
                        _ElementUtilities._addEventListener(appBarFirstDiv, "focusin", _setFocusToPreviousAppBar, false);
                        // add to beginning
                        if (this._element.children[0]) {
                            this._element.insertBefore(appBarFirstDiv, this._element.children[0]);
                        } else {
                            this._element.appendChild(appBarFirstDiv);
                        }
                    }
                    if (!appBarFinalDiv) {
                        // Add a finalDiv that will be the last child of the appBar.
                        // On focus set focus to the next appBar.
                        // The div should only be focusable if there are shown non-sticky AppBars.
                        appBarFinalDiv = _Global.document.createElement("div");
                        // display: inline is needed so that the div doesn't take up space and cause the page to scroll on focus
                        appBarFinalDiv.style.display = "inline";
                        appBarFinalDiv.className = _Constants.finalDivClass;
                        appBarFinalDiv.tabIndex = -1;
                        appBarFinalDiv.setAttribute("aria-hidden", "true");
                        _ElementUtilities._addEventListener(appBarFinalDiv, "focusin", _setFocusToNextAppBar, false);
                        this._element.appendChild(appBarFinalDiv);
                    }


                    // invokeButton should be the second to last element in the AppBar's tab order. Second to the finalDiv.
                    if (this._element.children[this._element.children.length - 2] !== this._invokeButton) {
                        this._element.insertBefore(this._invokeButton, appBarFinalDiv);
                    }
                    var elms = this._element.getElementsByTagName("*");
                    var highestTabIndex = _ElementUtilities._getHighestTabIndexInList(elms);
                    this._invokeButton.tabIndex = highestTabIndex;

                    // Update the tabIndex of the firstDiv & finalDiv
                    if (_isThereVisibleNonStickyBar()) {

                        if (appBarFirstDiv) {
                            appBarFirstDiv.tabIndex = _ElementUtilities._getLowestTabIndexInList(elms);
                        }
                        if (appBarFinalDiv) {
                            appBarFinalDiv.tabIndex = highestTabIndex;
                        }
                    } else {
                        if (appBarFirstDiv) {
                            appBarFirstDiv.tabIndex = -1;
                        }
                        if (appBarFinalDiv) {
                            appBarFinalDiv.tabIndex = -1;
                        }
                    }
                },

                _writeProfilerMark: function AppBar_writeProfilerMark(text) {
                    _WriteProfilerMark("WinJS.UI.AppBar:" + this._id + ":" + text);
                }
            }, {
                // Statics
                _appBarsSynchronizationPromise: Promise.as(),

                // Returns true if the element or what had focus before the element (if a Flyout) is either:
                //   1) the appBar or subtree
                //   2) OR in a flyout spawned by the appBar
                // Returns false otherwise.
                _isWithinAppBarOrChild: function (element, appBar) {
                    if (!element || !appBar) {
                        return false;
                    }
                    if (appBar.contains(element)) {
                        return true;
                    }
                    var flyout = _Overlay._Overlay._getParentControlUsingClassName(element, _Constants.flyoutClass);
                    return (flyout && appBar.contains(flyout._previousFocus));
                },

                // Callback for AppBar invokeButton and Edgy Event Command
                _toggleAllAppBarsState: function (keyboardInvoked, sourceAppBar) {
                    var bars = _getDynamicBarsForEdgy();

                    var hiding;
                    if (sourceAppBar) {
                        // If the sourceAppBar is shown, hide all AppBars, else show all AppBars.
                        hiding = _ElementUtilities.hasClass(sourceAppBar._element, _Constants.showingClass) || _ElementUtilities.hasClass(sourceAppBar._element, _Constants.shownClass);
                    } else {
                        // EDGY event behavior. No sourceAppBar specified.
                        // If every AppBar is shown, hide them. Otherwise show them all.
                        hiding = bars._shown && !bars._hidden;
                    }

                    if (hiding) {
                        AppBar._appBarsSynchronizationPromise = AppBar._appBarsSynchronizationPromise.then(function () {
                            return _Overlay._Overlay._hideAllBars(bars, keyboardInvoked);
                        });
                        return "hiding";
                    } else {
                        AppBar._appBarsSynchronizationPromise = AppBar._appBarsSynchronizationPromise.then(function () {
                            return _Overlay._Overlay._showAllBars(bars, keyboardInvoked);
                        });
                        return "showing";
                    }
                },
            });

            return AppBar;
        })
    });

});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Menu Command
/// <dictionary>appbar,appbars,Flyout,Flyouts,onclick,Statics</dictionary>
define('WinJS/Controls/Menu/_Command',[
    'exports',
    '../../Core/_Global',
    '../../Core/_Base',
    '../../Core/_ErrorFromName',
    '../../Core/_Resources',
    '../../Utilities/_Control',
    '../../Utilities/_ElementUtilities',
    '../AppBar/_Constants',
    '../Flyout/_Overlay'
    ], function menuCommandInit(exports, _Global, _Base, _ErrorFromName, _Resources, _Control, _ElementUtilities, _Constants, _Overlay) {
    "use strict";

    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {
        /// <field>
        /// <summary locid="WinJS.UI.MenuCommand">
        /// Represents a command to be displayed in a Menu. MenuCommand objects provide button, toggle button, flyout button,
        /// or separator functionality for Menu controls.
        /// </summary>
        /// <compatibleWith platform="Windows" minVersion="8.0"/>
        /// </field>
        /// <icon src="ui_winjs.ui.menucommand.12x12.png" width="12" height="12" />
        /// <icon src="ui_winjs.ui.menucommand.16x16.png" width="16" height="16" />
        /// <htmlSnippet><![CDATA[<button data-win-control="WinJS.UI.MenuCommand" data-win-options="{type:'button',label:'Button'}"></button>]]></htmlSnippet>
        /// <part name="MenuCommand" class="win-command" locid="WinJS.UI.MenuCommand_name">The MenuCommand control itself</part>
        /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
        /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
        /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
        MenuCommand: _Base.Namespace._lazy(function () {

            function _handleMenuClick(event) {
                /*jshint validthis: true */
                var command = this.winControl;
                if (command) {
                    var hideParent = true;
                    if (command._type === _Constants.typeToggle) {
                        command.selected = !command.selected;
                    } else if (command._type === _Constants.typeFlyout && command._flyout) {
                        var flyout = command._flyout;
                        // Flyout may not have processAll'd, so this may be a DOM object
                        if (typeof flyout === "string") {
                            flyout = _Global.document.getElementById(flyout);
                        }
                        if (!flyout.show) {
                            flyout = flyout.winControl;
                        }
                        if (flyout && flyout.show) {
                            if (command._parentFlyout) {
                                hideParent = false;
                                flyout.show(command._parentFlyout._currentAnchor, command._parentFlyout._currentPlacement, command._parentFlyout._currentAlignment);
                            } else {
                                flyout.show(this);
                            }
                        }
                    }
                    if (command.onclick) {
                        command.onclick(event);
                    }
                    // Dismiss parent flyout
                    if (hideParent && command._parentFlyout) {
                        command._parentFlyout.hide();
                    }
                }
            }

            function _handleMouseOver() {
                /*jshint validthis: true */
                if (this && this.focus) {
                    this.focus();

                    this.addEventListener("mousemove", _handleMouseMove, false);
                }
            }

            function _handleMouseMove() {
                /*jshint validthis: true */
                if (this && this.focus && this !== _Global.document.activeElement) {
                    this.focus();
                }
            }

            function _handleMouseOut() {
                /*jshint validthis: true */
                var that = this;
                var parentFlyout = _getParentFlyout(that);
                if (parentFlyout
                 && this === _Global.document.activeElement
                 && _ElementUtilities.hasClass(parentFlyout, _Constants.menuClass)
                 && parentFlyout.focus) {
                    // Menu gives focus to the menu itself
                    parentFlyout.focus();
                } else if (parentFlyout
                        && this === _Global.document.activeElement
                        && parentFlyout.children
                        && parentFlyout.children.length > 0
                        && parentFlyout.children[0]
                        && _ElementUtilities.hasClass(parentFlyout.children[0], _Constants.firstDivClass)
                        && parentFlyout.children[0].focus) {
                    // Flyout gives focus to firstDiv
                    parentFlyout.children[0].focus();
                }

                this.removeEventListener("mousemove", _handleMouseMove, false);
            }

            function _getParentFlyout(element) {
                while (element && !_ElementUtilities.hasClass(element, _Constants.flyoutClass)) {
                    element = element.parentElement;
                }

                return element;
            }

            var strings = {
                get ariaLabel() { return _Resources._getWinJSString("ui/menuCommandAriaLabel").value; },
                get duplicateConstruction() { return "Invalid argument: Controls may only be instantiated one time for each DOM element"; },
                get badClick() { return "Invalid argument: The onclick property for an {0} must be a function"; },
                get badHrElement() { return "Invalid argument: For a separator, the element must be null or an hr element"; },
                get badButtonElement() { return "Invalid argument: For a button, toggle, or flyout command, the element must be null or a button element"; }
            };

            return _Base.Class.define(function MenuCommand_ctor(element, options) {
                /// <signature helpKeyword="WinJS.UI.AppBarCommand.MenuCommand">
                /// <summary locid="WinJS.UI.MenuCommand.constructor">
                /// Creates a new MenuCommand object.
                /// </summary>
                /// <param name="element" domElement="true" locid="WinJS.UI.MenuCommand.constructor_p:element">
                /// The DOM element that will host the control.
                /// </param>
                /// <param name="options" type="Object" locid="WinJS.UI.MenuCommand.constructor_p:options">
                /// The set of properties and values to apply to the new MenuCommand.
                /// </param>
                /// <returns type="WinJS.UI.MenuCommand" locid="WinJS.UI.MenuCommand.constructor_returnValue">
                /// A MenuCommand control.
                /// </returns>
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </signature>

                // Check to make sure we weren't duplicated
                if (element && element.winControl) {
                    throw new _ErrorFromName("WinJS.UI.MenuCommand.DuplicateConstruction", strings.duplicateConstruction);
                }

                this._disposed = false;

                // Don't blow up if they didn't pass options
                if (!options) {
                    options = {};
                }

                // Need a type before we can create our element
                if (!options.type) {
                    this._type = _Constants.typeButton;
                }

                // Go ahead and create it, separator types look different than buttons
                // Don't forget to use passed in element if one was provided.
                this._element = element;
                if (options.type === _Constants.typeSeparator) {
                    this._createSeparator();
                } else {
                    // This will also set the icon & label
                    this._createButton();
                }
                _ElementUtilities.addClass(this._element, "win-disposable");

                // Remember ourselves
                this._element.winControl = this;

                // Attach our css class
                _ElementUtilities.addClass(this._element, _Constants.menuCommandClass);

                if (!options.selected && options.type === _Constants.typeToggle) {
                    // Make sure toggle's have selected false for CSS
                    this.selected = false;
                }
                if (options.onclick) {
                    this.onclick = options.onclick;
                }
                options.onclick = _handleMenuClick;

                _Control.setOptions(this, options);

                // Set our options
                if (this._type !== _Constants.typeSeparator) {
                    // Make sure we have an ARIA role
                    var role = this._element.getAttribute("role");
                    if (role === null || role === "" || role === undefined) {
                        role = "menuitem";
                        if (this._type === _Constants.typeToggle) {
                            role = "menuitemcheckbox";
                        }
                        this._element.setAttribute("role", role);
                        if (this._type === _Constants.typeFlyout) {
                            this._element.setAttribute("aria-haspopup", true);
                        }
                    }
                    var label = this._element.getAttribute("aria-label");
                    if (label === null || label === "" || label === undefined) {
                        this._element.setAttribute("aria-label", strings.ariaLabel);
                    }
                }

                this._element.addEventListener("mouseover", _handleMouseOver, false);
                this._element.addEventListener("mouseout", _handleMouseOut, false);
            }, {
                /// <field type="String" locid="WinJS.UI.MenuCommand.id" helpKeyword="WinJS.UI.MenuCommand.id" isAdvanced="true">
                /// Gets the  ID of the MenuCommand.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                id: {
                    get: function () {
                        return this._element.id;
                    },
                    set: function (value) {
                        // we allow setting first time only. otherwise we ignore it.
                        if (!this._element.id) {
                            this._element.id = value;
                        }
                    }
                },

                /// <field type="String" readonly="true" defaultValue="button" oamOptionsDatatype="WinJS.UI.MenuCommand.type" locid="WinJS.UI.MenuCommand.type" helpKeyword="WinJS.UI.MenuCommand.type" isAdvanced="true">
                /// Gets the type of the MenuCommand. Possible values are "button", "toggle", "flyout", or "separator".
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                type: {
                    get: function () {
                        return this._type;
                    },
                    set: function (value) {
                        // we allow setting first time only. otherwise we ignore it.
                        if (!this._type) {
                            if (value !== _Constants.typeButton && value !== _Constants.typeFlyout && value !== _Constants.typeToggle && value !== _Constants.typeSeparator) {
                                this._type = _Constants.typeButton;
                            } else {
                                this._type = value;
                            }
                        }
                    }
                },

                /// <field type="String" locid="WinJS.UI.MenuCommand.label" helpKeyword="WinJS.UI.MenuCommand.label">
                /// The label of the MenuCommand
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                label: {
                    get: function () {
                        return this._label;
                    },
                    set: function (value) {
                        this._label = value;
                        this._element.textContent = this.label;

                        // Update aria-label
                        this._element.setAttribute("aria-label", this.label);
                    }
                },

                /// <field type="Function" locid="WinJS.UI.MenuCommand.onclick" helpKeyword="WinJS.UI.MenuCommand.onclick">
                /// Gets or sets the function to invoke when the command is clicked.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                onclick: {
                    get: function () {
                        return this._onclick;
                    },
                    set: function (value) {
                        if (value && typeof value !== "function") {
                            throw new _ErrorFromName("WinJS.UI.MenuCommand.BadClick", _Resources._formatString(strings.badClick, "MenuCommand"));
                        }
                        this._onclick = value;
                    }
                },

                /// <field type="Object" locid="WinJS.UI.MenuCommand.flyout" helpKeyword="WinJS.UI.MenuCommand.flyout">
                /// For flyout type MenuCommands, this property  returns the WinJS.UI.Flyout that this command invokes. When setting this property, you can set
                /// it to the string ID of the Flyout, the DOM object that hosts the Flyout, or the Flyout object itself.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                flyout: {
                    get: function () {
                        // Resolve it to the flyout
                        var flyout = this._flyout;
                        if (typeof flyout === "string") {
                            flyout = _Global.document.getElementById(flyout);
                        }
                        // If it doesn't have a .element, then we need to getControl on it
                        if (flyout && !flyout.element) {
                            flyout = flyout.winControl;
                        }

                        return flyout;
                    },
                    set: function (value) {
                        // Need to update aria-owns with the new ID.
                        var id = value;
                        if (id && typeof id !== "string") {
                            // Our controls have .element properties
                            if (id.element) {
                                id = id.element;
                            }
                            // Hope it's a DOM element, get ID from DOM element
                            if (id) {
                                if (id.id) {
                                    id = id.id;
                                } else {
                                    // No id, have to fake one
                                    id.id = _ElementUtilities._uniqueID(id);
                                    id = id.id;
                                }
                            }
                        }
                        if (typeof id === "string") {
                            this._element.setAttribute("aria-owns", id);
                        }

                        // Remember it
                        this._flyout = value;
                    }
                },

                /// <field type="Boolean" locid="WinJS.UI.MenuCommand.selected" helpKeyword="WinJS.UI.MenuCommand.selected">
                /// Gets or sets the selected state of a toggle button. This property is true if the toggle button is selected; otherwise, it's false.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                selected: {
                    get: function () {
                        // Ensure it's a boolean because we're using the DOM element to keep in-sync
                        return this._element.getAttribute("aria-checked") === "true";
                    },
                    set: function (value) {
                        this._element.setAttribute("aria-checked", !!value);
                    }
                },

                /// <field type="HTMLElement" domElement="true" readonly="true" hidden="true" locid="WinJS.UI.MenuCommand.element" helpKeyword="WinJS.UI.MenuCommand.element">
                /// Gets the DOM element that hosts this MenuCommand.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                element: {
                    get: function () {
                        return this._element;
                    }
                },

                /// <field type="Boolean" locid="WinJS.UI.MenuCommand.disabled" helpKeyword="WinJS.UI.MenuCommand.disabled">
                /// Gets or sets a value that indicates whether the MenuCommand is disabled. This value is true if the MenuCommand is disabled; otherwise, false.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                disabled: {
                    get: function () {
                        // Ensure it's a boolean because we're using the DOM element to keep in-sync
                        return !!this._element.disabled;
                    },
                    set: function (value) {
                        this._element.disabled = !!value;
                    }
                },

                /// <field type="Boolean" hidden="true" locid="WinJS.UI.MenuCommand.hidden" helpKeyword="WinJS.UI.MenuCommand.hidden">
                /// Determine if a command is currently hidden.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                hidden: {
                    get: function () {
                        // Ensure it's a boolean because we're using the DOM element to keep in-sync
                        return this._element.style.visibility === "hidden";
                    },
                    set: function (value) {
                        var menuControl = _Overlay._Overlay._getParentControlUsingClassName(this._element, _Constants.menuClass);
                        if (menuControl && !menuControl.hidden) {
                            throw new _ErrorFromName("WinJS.UI.MenuCommand.CannotChangeHiddenProperty", _Resources._formatString(_Overlay._Overlay.commonstrings.cannotChangeHiddenProperty, "Menu"));
                        }

                        var style = this._element.style;
                        if (value) {
                            style.visibility = "hidden";
                            style.display = "none";
                        } else {
                            style.visibility = "";
                            style.display = "block";
                        }
                    }
                },

                /// <field type="String" locid="WinJS.UI.MenuCommand.extraClass" isAdvanced="true" helpKeyword="WinJS.UI.MenuCommand.extraClass">
                /// Gets or sets the extra CSS class that is applied to the host DOM element.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                extraClass: {
                    get: function () {
                        return this._extraClass;
                    },
                    set: function (value) {
                        if (this._extraClass) {
                            _ElementUtilities.removeClass(this._element, this._extraClass);
                        }
                        this._extraClass = value;
                        _ElementUtilities.addClass(this._element, this._extraClass);
                    }
                },


                dispose: function () {
                    /// <signature helpKeyword="WinJS.UI.MenuCommand.dispose">
                    /// <summary locid="WinJS.UI.MenuCommand.dispose">
                    /// Disposes this control.
                    /// </summary>
                    /// <compatibleWith platform="Windows" minVersion="8.0"/>
                    /// </signature>
                    if (this._disposed) {
                        return;
                    }
                    this._disposed = true;

                    if (this._flyout) {
                        this._flyout.dispose();
                    }
                },

                addEventListener: function (type, listener, useCapture) {
                    /// <signature helpKeyword="WinJS.UI.MenuCommand.addEventListener">
                    /// <summary locid="WinJS.UI.MenuCommand.addEventListener">
                    /// Registers an event handler for the specified event.
                    /// </summary>
                    /// <param name="type" type="String" locid="WinJS.UI.MenuCommand.addEventListener_p:type">The name of the event to register.</param>
                    /// <param name="listener" type="Function" locid="WinJS.UI.MenuCommand.addEventListener_p:listener">The function that handles the event.</param>
                    /// <param name="useCapture" type="Boolean" locid="WinJS.UI.MenuCommand.addEventListener_p:useCapture">
                    /// Set to true to register the event handler for the capturing phase; otherwise, set to false to register the  event handler for the bubbling phase.
                    /// </param>
                    /// <compatibleWith platform="Windows" minVersion="8.0"/>
                    /// </signature>
                    return this._element.addEventListener(type, listener, useCapture);
                },

                removeEventListener: function (type, listener, useCapture) {
                    /// <signature helpKeyword="WinJS.UI.MenuCommand.removeEventListener">
                    /// <summary locid="WinJS.UI.MenuCommand.removeEventListener">
                    /// Removes the specified event handler that the addEventListener method registered.
                    /// </summary>
                    /// <param name="type" type="String" locid="WinJS.UI.MenuCommand.removeEventListener_p:type">The name of the event to remove.</param>
                    /// <param name="listener" type="Function" locid="WinJS.UI.MenuCommand.removeEventListener_p:listener">The event handler function to remove.</param>
                    /// <param name="useCapture" type="Boolean" locid="WinJS.UI.MenuCommand.removeEventListener_p:useCapture">
                    /// Set to true to remove the capturing phase event handler; set to false to remove the bubbling phase event handler.
                    /// </param>
                    /// <compatibleWith platform="Windows" minVersion="8.0"/>
                    /// </signature>
                    return this._element.removeEventListener(type, listener, useCapture);
                },

                // Private properties
                _createSeparator: function MenuCommand_createSeparator() {
                    // Make sure there's an input element
                    if (!this._element) {
                        this._element = _Global.document.createElement("hr");
                    } else {
                        // Verify the input was an hr
                        if (this._element.tagName !== "HR") {
                            throw new _ErrorFromName("WinJS.UI.MenuCommand.BadHrElement", strings.badHrElement);
                        }
                    }
                },

                _createButton: function MenuCommand_createButton() {
                    // Make sure there's an input element
                    if (!this._element) {
                        this._element = _Global.document.createElement("button");
                    } else {
                        // Verify the input was a button
                        if (this._element.tagName !== "BUTTON") {
                            throw new _ErrorFromName("WinJS.UI.MenuCommand.BadButtonElement", strings.badButtonElement);
                        }
                        this._element.innerHTML = "";
                    }

                    // MenuCommand buttons need to look like this:
                    //// <button type="button" onclick="" class="win-command">Command 1</button>
                    this._element.type = "button";

                    // 'textContent' label is added later by caller
                }
            });
        })
    });

});


// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Menu
/// <dictionary>Menu,Menus,Flyout,Flyouts,Statics</dictionary>
define('WinJS/Controls/Menu',[
    '../Core/_Global',
    '../Core/_Base',
    '../Core/_BaseUtils',
    '../Core/_ErrorFromName',
    '../Core/_Resources',
    '../Core/_WriteProfilerMark',
    '../Utilities/_ElementUtilities',
    '../Utilities/_Hoverable',
    './AppBar/_Constants',
    './Flyout',
    './Flyout/_Overlay',
    './Menu/_Command',
    'require-style!less/desktop/controls',
    'require-style!less/phone/controls'
    ], function menuInit(_Global,_Base, _BaseUtils, _ErrorFromName, _Resources, _WriteProfilerMark, _ElementUtilities, _Hoverable, _Constants, Flyout, _Overlay, _Command) {
    "use strict";

    _Base.Namespace.define("WinJS.UI", {
        /// <field>
        /// <summary locid="WinJS.UI.Menu">Represents a menu flyout for displaying commands.</summary>
        /// <compatibleWith platform="Windows" minVersion="8.0"/>
        /// </field>
        /// <name locid="WinJS.UI.Menu_name">Menu</name>
        /// <icon src="ui_winjs.ui.menu.12x12.png" width="12" height="12" />
        /// <icon src="ui_winjs.ui.menu.16x16.png" width="16" height="16" />
        /// <htmlSnippet supportsContent="true"><![CDATA[<div data-win-control="WinJS.UI.Menu">
        /// <button data-win-control="WinJS.UI.MenuCommand" data-win-options="{id:'',label:'example',type:'button',onclick:null}"></button>
        /// </div>]]></htmlSnippet>
        /// <event name="beforeshow" locid="WinJS.UI.Menu_e:beforeshow">Raised just before showing a menu.</event>
        /// <event name="aftershow" locid="WinJS.UI.Menu_e:aftershow">Raised immediately after a menu is fully shown.</event>
        /// <event name="beforehide" locid="WinJS.UI.Menu_e:beforehide">Raised just before hiding a menu.</event>
        /// <event name="afterhide" locid="WinJS.UI.Menu_e:afterhide">Raised immediately after a menu is fully hidden.</event>
        /// <part name="menu" class="win-menu" locid="WinJS.UI.Menu_part:menu">The Menu control itself</part>
        /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
        /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
        /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
        Menu: _Base.Namespace._lazy(function () {
            var Key = _ElementUtilities.Key;

            var strings = {
                get ariaLabel() { return _Resources._getWinJSString("ui/menuAriaLabel").value; },
                get requiresCommands() { return "Invalid argument: commands must not be empty"; },
                get nullCommand() { return "Invalid argument: command must not be null"; },
            };

            var Menu = _Base.Class.derive(Flyout.Flyout, function Menu_ctor(element, options) {
                /// <signature helpKeyword="WinJS.UI.Menu.Menu">
                /// <summary locid="WinJS.UI.Menu.constructor">
                /// Creates a new Menu control.
                /// </summary>
                /// <param name="element" type="HTMLElement" domElement="true" locid="WinJS.UI.Menu.constructor_p:element">
                /// The DOM element that will host the control.
                /// </param>
                /// <param name="options" type="Object" domElement="false" locid="WinJS.UI.Menu.constructor_p:options">
                /// The set of properties and values to apply to the control.
                /// </param>
                /// <returns type="WinJS.UI.Menu" locid="WinJS.UI.Menu.constructor_returnValue">The new Menu control.</returns>
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </signature>

                // We need to be built on top of a Flyout, so stomp on the user's input
                options = options || {};

                // Make sure there's an input element
                this._element = element || _Global.document.createElement("div");
                this._id = this._element.id || _ElementUtilities._uniqueID(this._element);
                this._writeProfilerMark("constructor,StartTM");

                // validate that if they didn't set commands, in which
                // case any HTML only contains commands.  Do this first
                // so that we don't leave partial Menus in the DOM.
                if (!options.commands && this._element) {
                    // Shallow copy object so we can modify it.
                    options = _BaseUtils._shallowCopy(options);
                    options.commands = this._verifyCommandsOnly(this._element, "WinJS.UI.MenuCommand");
                }

                // Remember aria role in case base constructor changes it
                var role = this._element ? this._element.getAttribute("role") : null;
                var label = this._element ? this._element.getAttribute("aria-label") : null;

                // Call the base overlay constructor helper
                this._baseFlyoutConstructor(this._element, options);

                // Make sure we have an ARIA role
                if (role === null || role === "" || role === undefined) {
                    this._element.setAttribute("role", "menu");
                }
                if (label === null || label === "" || label === undefined) {
                    this._element.setAttribute("aria-label", strings.ariaLabel);
                }

                // Handle "esc" & "up/down" key presses
                this._element.addEventListener("keydown", this._handleKeyDown, true);

                // Attach our css class
                _ElementUtilities.addClass(this._element, _Constants.menuClass);

                // Need to set our commands, making sure we're hidden first
                this.hide();
                this._writeProfilerMark("constructor,StopTM");
            }, {
                // Public Properties

                /// <field type="Array" locid="WinJS.UI.Menu.commands" helpKeyword="WinJS.UI.Menu.commands" isAdvanced="true">
                /// Sets the MenuCommand objects that appear in the Menu. You can set this to a single MenuCommand or an array of MenuCommand objects.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                commands: {
                    set: function (value) {
                        // Fail if trying to set when visible
                        if (!this.hidden) {
                            throw new _ErrorFromName("WinJS.UI.Menu.CannotChangeCommandsWhenVisible", _Resources._formatString(_Overlay._Overlay.commonstrings.cannotChangeCommandsWhenVisible, "Menu"));
                        }

                        // Start from scratch
                        _ElementUtilities.empty(this._element);

                        // In case they had only one...
                        if (!Array.isArray(value)) {
                            value = [value];
                        }

                        // Add commands
                        var len = value.length;
                        for (var i = 0; i < len; i++) {
                            this._addCommand(value[i]);
                        }
                    }
                },

                getCommandById: function (id) {
                    /// <signature helpKeyword="WinJS.UI.Menu.getCommandById">
                    /// <summary locid="WinJS.UI.Menu.getCommandById">
                    /// Retrieve the command with the specified ID from this Menu.  If more than one command is found, all are returned.
                    /// </summary>
                    /// <param name="id" type="String" locid="WinJS.UI.Menu.getCommandById_p:id">The ID of the command to find.</param>
                    /// <returns type="object" locid="WinJS.UI.Menu.getCommandById_returnValue">
                    /// The command found, an array of commands if more than one have the same ID, or null if no command is found.
                    /// </returns>
                    /// <compatibleWith platform="Windows" minVersion="8.0"/>
                    /// </signature>
                    var commands = this.element.querySelectorAll("#" + id);
                    var newCommands = [];
                    for (var count = 0, len = commands.length; count < len; count++) {
                        if (commands[count].winControl) {
                            newCommands.push(commands[count].winControl);
                        }
                    }

                    if (newCommands.length === 1) {
                        return newCommands[0];
                    } else if (newCommands.length === 0) {
                        return null;
                    }

                    return newCommands;
                },


                showCommands: function (commands) {
                    /// <signature helpKeyword="WinJS.UI.Menu.showCommands">
                    /// <summary locid="WinJS.UI.Menu.showCommands">
                    /// Shows the specified commands of the Menu.
                    /// </summary>
                    /// <param name="commands" type="Array" locid="WinJS.UI.Menu.showCommands_p:commands">
                    /// The commands to show. The array elements may be Menu objects, or the string identifiers (IDs) of commands.
                    /// </param>
                    /// <compatibleWith platform="Windows" minVersion="8.0"/>
                    /// </signature>
                    if (!commands) {
                        throw new _ErrorFromName("WinJS.UI.Menu.RequiresCommands", strings.requiresCommands);
                    }

                    this._showCommands(commands, true);
                },

                hideCommands: function (commands) {
                    /// <signature helpKeyword="WinJS.UI.Menu.hideCommands">
                    /// <summary locid="WinJS.UI.Menu.hideCommands">
                    /// Hides the Menu.
                    /// </summary>
                    /// <param name="commands" type="Array" locid="WinJS.UI.Menu.hideCommands_p:commands">
                    /// Required. Command or Commands to hide, either String, DOM elements, or WinJS objects.
                    /// </param>
                    /// <compatibleWith platform="Windows" minVersion="8.0"/>
                    /// </signature>
                    if (!commands) {
                        throw new _ErrorFromName("WinJS.UI.Menu.RequiresCommands", strings.requiresCommands);
                    }

                    this._hideCommands(commands, true);
                },

                showOnlyCommands: function (commands) {
                    /// <signature helpKeyword="WinJS.UI.Menu.showOnlyCommands">
                    /// <summary locid="WinJS.UI.Menu.showOnlyCommands">
                    /// Shows the specified commands of the Menu while hiding all other commands.
                    /// </summary>
                    /// <param name="commands" type="Array" locid="WinJS.UI.Menu.showOnlyCommands_p:commands">
                    /// The commands to show. The array elements may be MenuCommand objects, or the string identifiers (IDs) of commands.
                    /// </param>
                    /// <compatibleWith platform="Windows" minVersion="8.0"/>
                    /// </signature>
                    if (!commands) {
                        throw new _ErrorFromName("WinJS.UI.Menu.RequiresCommands", strings.requiresCommands);
                    }

                    this._showOnlyCommands(commands, true);
                },

                show: function (anchor, placement, alignment) {
                    /// <signature helpKeyword="WinJS.UI.Menu.show">
                    /// <summary locid="WinJS.UI.Menu.show">
                    /// Shows the Menu, if hidden, regardless of other states.
                    /// </summary>
                    /// <param name="anchor" type="HTMLElement" domElement="true" locid="WinJS.UI.Menu.show_p:anchor">
                    /// The DOM element, or ID of a DOM element,  to anchor the Menu. This parameter overrides the anchor property for this method call only.
                    /// </param>
                    /// <param name="placement" type="object" domElement="false" locid="WinJS.UI.Menu.show_p:placement">
                    /// The placement of the Menu to the anchor: 'auto' (default), 'top', 'bottom', 'left', or 'right'. This parameter overrides the placement
                    /// property for this method call only.
                    /// </param>
                    /// <param name="alignment" type="object" domElement="false" locid="WinJS.UI.Menu.show_p:alignment">
                    /// For 'top' or 'bottom' placement, the alignment of the Menu to the anchor's edge: 'center' (default), 'left', or 'right'. This parameter
                    /// overrides the alignment property for this method call only.
                    /// </param>
                    /// <compatibleWith platform="Windows" minVersion="8.0"/>
                    /// </signature>
                    // Just call private version to make appbar flags happy
                    this._writeProfilerMark("show,StartTM"); // The corresponding "stop" profiler mark is handled in _Overlay._baseEndShow().
                    this._show(anchor, placement, alignment);
                },

                _show: function Menu_show(anchor, placement, alignment) {
                    // Before we show, we also need to check for children flyouts needing anchors
                    this._checkForFlyoutCommands();

                    // Call flyout show
                    this._baseFlyoutShow(anchor, placement, alignment);

                    // We need to check for toggles after we send the beforeshow event,
                    // so the developer has a chance to show or hide more commands.
                    // Flyout's _findPosition will make that call.
                },

                _addCommand: function Menu_addCommand(command) {
                    if (!command) {
                        throw new _ErrorFromName("WinJS.UI.Menu.NullCommand", strings.nullCommand);
                    }
                    // See if it's a command already
                    if (!command._element) {
                        // Not a command, so assume it's options for a command
                        command = new _Command.MenuCommand(null, command);
                    }
                    // If we were attached somewhere else, detach us
                    if (command._element.parentElement) {
                        command._element.parentElement.removeChild(command._element);
                    }

                    // Reattach us
                    this._element.appendChild(command._element);
                },

                // Called by flyout's _findPosition so that application can update it status
                // we do the test and we can then fix this last-minute before showing.
                _checkToggle: function Menu_checkToggle() {
                    var toggles = this._element.querySelectorAll(".win-command[aria-checked]");
                    var hasToggle = false;
                    if (toggles) {
                        for (var i = 0; i < toggles.length; i++) {
                            if (toggles[i] && toggles[i].winControl && !toggles[i].winControl.hidden) {
                                // Found a visible toggle control
                                hasToggle = true;
                                break;
                            }
                        }
                    }
                    if (hasToggle) {
                        _ElementUtilities.addClass(this._element, _Constants.menuToggleClass);
                    } else {
                        _ElementUtilities.removeClass(this._element, _Constants.menuToggleClass);
                    }
                },

                _checkForFlyoutCommands: function Menu_checkForFlyoutCommands() {
                    var commands = this._element.querySelectorAll(".win-command");
                    for (var count = 0; count < commands.length; count++) {
                        if (commands[count].winControl) {
                            // Remember our anchor in case it's a flyout
                            commands[count].winControl._parentFlyout = this;
                        }
                    }
                },

                _handleKeyDown: function Menu_handleKeyDown(event) {
                    var that = this;
                    if (event.keyCode === Key.escape) {
                        // Show a focus rect on what we move focus to
                        this.winControl._keyboardInvoked = true;
                        this.winControl._hide();
                    } else if ((event.keyCode === Key.space || event.keyCode === Key.enter)
                           && (this === _Global.document.activeElement)) {
                        event.preventDefault();
                        this.winControl.hide();
                    } else if (event.keyCode === Key.upArrow) {
                        Menu._focusOnPreviousElement(that);

                        // Prevent the page from scrolling
                        event.preventDefault();
                    } else if (event.keyCode === Key.downArrow) {
                        Menu._focusOnNextElement(that);

                        // Prevent the page from scrolling
                        event.preventDefault();
                    } else if (event.keyCode === Key.tab) {
                        event.preventDefault();
                    }
                },

                _writeProfilerMark: function Menu_writeProfilerMark(text) {
                    _WriteProfilerMark("WinJS.UI.Menu:" + this._id + ":" + text);
                }
            });

            // Statics

            // Set focus to next focusable element in the menu (loop if necessary).
            //   Note: The loop works by first setting focus to the menu itself.  If the menu is
            //         what had focus before, then we break.  Otherwise we try the first child next.
            // Focus remains on the menu if nothing is focusable.
            Menu._focusOnNextElement = function (menu) {
                var _currentElement = _Global.document.activeElement;

                do {
                    if (_currentElement === menu) {
                        _currentElement = _currentElement.firstElementChild;
                    } else {
                        _currentElement = _currentElement.nextElementSibling;
                    }

                    if (_currentElement) {
                        _currentElement.focus();
                    } else {
                        _currentElement = menu;
                    }

                } while (_currentElement !== _Global.document.activeElement);
            };

            // Set focus to previous focusable element in the menu (loop if necessary).
            //   Note: The loop works by first setting focus to the menu itself.  If the menu is
            //         what had focus before, then we break.  Otherwise we try the last child next.
            // Focus remains on the menu if nothing is focusable.
            Menu._focusOnPreviousElement = function (menu) {
                var _currentElement = _Global.document.activeElement;

                do {
                    if (_currentElement === menu) {
                        _currentElement = _currentElement.lastElementChild;
                    } else {
                        _currentElement = _currentElement.previousElementSibling;
                    }

                    if (_currentElement) {
                        _currentElement.focus();
                    } else {
                        _currentElement = menu;
                    }

                } while (_currentElement !== _Global.document.activeElement);
            };

            return Menu;
        })
    });

});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/SearchBox/_SearchSuggestionManagerShim',[
    'exports',
    '../../_Signal',
    '../../Core/_Base',
    '../../Core/_BaseUtils',
    '../../Core/_Events',
    '../../BindingList',
], function SearchSuggestionManagerShimInit(exports, _Signal, _Base, _BaseUtils, _Events, BindingList) {
    "use strict";

    var CollectionChange = {
        reset: 0,
        itemInserted: 1,
        itemRemoved: 2,
        itemChanged: 3
    };
    var SearchSuggestionKind = {
        Query: 0,
        Result: 1,
        Separator: 2
    };

    var SuggestionVectorShim = _Base.Class.derive(Array, function SuggestionVectorShim_ctor() {
    }, {
        reset: function () {
            this.length = 0;
            this.dispatchEvent("vectorchanged", { collectionChange: CollectionChange.reset, index: 0 });
        },

        insert: function (index, data) {
            this.splice(index, 0, data);
            this.dispatchEvent("vectorchanged", { collectionChange: CollectionChange.itemInserted, index: index });
        },

        remove: function (index) {
            this.splice(index, 1);
            this.dispatchEvent("vectorchanged", { collectionChange: CollectionChange.itemRemoved, index: index });
        },
    });
    _Base.Class.mix(SuggestionVectorShim, _Events.eventMixin);

    var SearchSuggestionCollectionShim = _Base.Class.define(function SearchSuggestionCollectionShim_ctor() {
        this._data = [];
    }, {
        size: {
            get: function () {
                return this._data.length;
            }
        },

        appendQuerySuggestion: function (text) {
            this._data.push({ kind: SearchSuggestionKind.Query, text: text });
        },
        appendQuerySuggestions: function (suggestions) {
            suggestions.forEach(this.appendQuerySuggestion.bind(this));
        },
        appendResultSuggestion: function (text, detailText, tag, imageUrl, imageAlternateText) {
            // 'image' must be null (not undefined) for SearchBox to fallback to use imageUrl instead
            this._data.push({ kind: SearchSuggestionKind.Result, text: text, detailText: detailText, tag: tag, imageUrl: imageUrl, imageAlternateText: imageAlternateText, image: null });
        },
        appendSearchSeparator: function (label) {
            this._data.push({ kind: SearchSuggestionKind.Separator, text: label });
        }
    });

    var SuggestionsRequestedEventArgShim = _Base.Class.define(function SuggestionsRequestedEventArgShim_ctor(queryText, language, linguisticDetails) {
        this._queryText = queryText;
        this._language = language;
        this._linguisticDetails = linguisticDetails;
        this._searchSuggestionCollection = new SearchSuggestionCollectionShim();
    }, {
        language: {
            get: function () {
                return this._language;
            }
        },
        linguisticDetails: {
            get: function () {
                return this._linguisticDetails;
            }
        },
        queryText: {
            get: function () {
                return this._queryText;
            }
        },
        searchSuggestionCollection: {
            get: function () {
                return this._searchSuggestionCollection;
            }
        },
        getDeferral: function () {
            return this._deferralSignal || (this._deferralSignal = new _Signal());
        },

        _deferralSignal: null,
    });

    var SearchSuggestionManagerShim = _Base.Class.define(function SearchSuggestionManagerShim_ctor() {
        this._updateVector = this._updateVector.bind(this);

        this._suggestionVector = new SuggestionVectorShim();
        this._query = "";
        this._history = { "": [] };

        this._dataSource = [];

        this.searchHistoryContext = "";
        this.searchHistoryEnabled = true;
    }, {
        addToHistory: function (queryText /*, language */) {
            if (!queryText || !queryText.trim()) {
                return;
            }

            var history = this._history[this.searchHistoryContext];
            var dupeIndex = -1;
            for (var i = 0, l = history.length; i < l; i++) {
                var item = history[i];
                if (item.text.toLowerCase() === queryText.toLowerCase()) {
                    dupeIndex = i;
                    break;
                }
            }
            if (dupeIndex >= 0) {
                history.splice(dupeIndex, 1);
            }

            history.splice(0, 0, { text: queryText, kind: SearchSuggestionKind.Query });
            this._updateVector();
        },

        clearHistory: function () {
            this._history[this.searchHistoryContext] = [];
            this._updateVector();
        },

        setLocalContentSuggestionSettings: function (settings) {
        },

        setQuery: function (queryText) {
            var that = this;
            function update(arr) {
                that._dataSource = arr;
                that._updateVector();
            }

            this._query = queryText;
            var arg = new SuggestionsRequestedEventArgShim(queryText);
            this.dispatchEvent("suggestionsrequested", { request: arg });
            if (arg._deferralSignal) {
                arg._deferralSignal.promise.then(update.bind(this, arg.searchSuggestionCollection._data));
            } else {
                update(arg.searchSuggestionCollection._data);
            }
        },

        searchHistoryContext: {
            get: function () {
                return "" + this._searchHistoryContext;
            },
            set: function (value) {
                value = "" + value;
                if (!this._history[value]) {
                    this._history[value] = [];
                }
                this._searchHistoryContext = value;
            }
        },

        searchHistoryEnabled: {
            get: function () {
                return this._searchHistoryEnabled;
            },
            set: function (value) {
                this._searchHistoryEnabled = value;
            }
        },

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

        _updateVector: function () {
            // Can never clear the entire suggestions list or it will cause a visual flash because
            // the SearchBox control removes the suggestions list UI when the SSM fires vectorChanged
            // with size === 0, then re-renders it when the first suggestion is added.
            // Workaround is to insert a dummy entry, remove all old entries, add the new set of
            // eligible suggestions, then remove the dummy entry.
            this.suggestions.insert(this.suggestions.length, { text: "", kind: SearchSuggestionKind.Query });

            while (this.suggestions.length > 1) {
                this.suggestions.remove(0);
            }

            var index = 0;
            var added = {};
            if (this.searchHistoryEnabled) {
                var q = this._query.toLowerCase();
                this._history[this.searchHistoryContext].forEach(function (item) {
                    var text = item.text.toLowerCase();
                    if (text.indexOf(q) === 0) {
                        this.suggestions.insert(index, item);
                        added[text] = true;
                        index++;
                    }
                }, this);
            }
            this._dataSource.forEach(function (item) {
                if (item.kind === SearchSuggestionKind.Query) {
                    if (!added[item.text.toLowerCase()]) {
                        this.suggestions.insert(index, item);
                        index++;
                    }
                } else {
                    this.suggestions.insert(index, item);
                    index++;
                }
            }, this);

            this.suggestions.remove(this.suggestions.length - 1);
        },
    });
    _Base.Class.mix(SearchSuggestionManagerShim, _Events.eventMixin);

    _Base.Namespace._moduleDefine(exports, null, {
        _CollectionChange: CollectionChange,
        _SearchSuggestionKind: SearchSuggestionKind,
        _SearchSuggestionManagerShim: SearchSuggestionManagerShim,
    });
});
// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/SearchBox',[
    '../Core/_Global',
    '../Core/_WinRT',
    '../Core/_Base',
    '../Core/_ErrorFromName',
    '../Core/_Events',
    '../Core/_Resources',
    '../Animations',
    '../BindingList',
    '../Controls/Repeater',
    '../Utilities/_Control',
    '../Utilities/_ElementListUtilities',
    '../Utilities/_ElementUtilities',
    '../Utilities/_Hoverable',
    './SearchBox/_SearchSuggestionManagerShim',
    'require-style!less/desktop/controls',
    'require-style!less/phone/controls',
], function searchboxInit(_Global, _WinRT, _Base, _ErrorFromName, _Events, _Resources, Animations, BindingList, Repeater, _Control, _ElementListUtilities, _ElementUtilities, _Hoverable, _SearchSuggestionManagerShim) {
    "use strict";

    _Base.Namespace.define("WinJS.UI", {
        /// <field>
        /// <summary locid="WinJS.UI.SearchBox">
        /// Enables the user to perform search queries and select suggestions.
        /// </summary>
        /// <compatibleWith platform="Windows" minVersion="8.1"/>
        /// </field>
        /// <icon src="ui_winjs.ui.search.12x12.png" width="12" height="12" />
        /// <icon src="ui_winjs.ui.search.16x16.png" width="16" height="16" />
        /// <htmlSnippet><![CDATA[<div data-win-control="WinJS.UI.SearchBox"></div>]]></htmlSnippet>
        /// <event name="querychanged" bubbles="true" locid="WinJS.UI.SearchBox_e:querychanged">Raised when user or app changes the query text.</event>
        /// <event name="querysubmitted" bubbles="true" locid="WinJS.UI.SearchBox_e:querysubmitted">Raised when user clicks on search glyph or presses Enter.</event>
        /// <event name="resultsuggestionchosen" bubbles="true" locid="WinJS.UI.SearchBox_e:resultsuggestionchosen">Raised when user clicks  one of the displayed suggestions.</event>
        /// <event name="suggestionsrequested" bubbles="true" locid="WinJS.UI.SearchBox_e:suggestionsrequested">Raised when the system requests search suggestions from this app.</event>
        /// <event name="receivingfocusonkeyboardinput" bubbles="true" locid="WinJS.UI.SearchBox_e:receivingfocusonkeyboardinput">
        /// Raised when the app automatically redirects focus to the search box. This event can only be raised when the focusOnKeyboardInput property is set to true.
        /// </event>
        /// <part name="searchbox" class="win-searchbox" locid="WinJS.UI.SearchBox:search">Styles the entire Search box control.</part>
        /// <part name="searchbox-input" class="win-searchbox-input" locid="WinJS.UI.SearchBox_part:Input">Styles the query input box.</part>
        /// <part name="searchbox-button" class="win-searchbox-button" locid="WinJS.UI.SearchBox_part:Button">Styles the search button.</part>
        /// <part name="searchbox-flyout" class="win-searchbox-flyout" locid="WinJS.UI.SearchBox_part:Flyout">Styles the result suggestions flyout.</part>
        /// <part name="searchbox-suggestion-result" class="win-searchbox-suggestion-result" locid="WinJS.UI.SearchBox_part:Suggestion_Result">Styles the result type suggestion.</part>
        /// <part name="searchbox-suggestion-query" class="win-searchbox-suggestion-query" locid="WinJS.UI.SearchBox_part:Suggestion_Query">Styles the query type suggestion.</part>
        /// <part name="searchbox-suggestion-separator" class="win-searchbox-suggestion-separator" locid="WinJS.UI.SearchBox_part:Suggestion_Separator">
        /// Styles the separator type suggestion.
        /// </part>
        /// <part name="searchbox-suggestion-selected" class="win-searchbox-suggestion-selected" locid="WinJS.UI.SearchBox_part:Suggestion_Selected">
        /// Styles the currently selected suggestion.
        /// </part>
        /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
        /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
        /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
        SearchBox: _Base.Namespace._lazy(function () {
            var createEvent = _Events._createEventProperty;
            var Key = _ElementUtilities.Key;

            // Enums
            var ClassName = {
                searchBox: "win-searchbox",
                searchBoxInput: "win-searchbox-input",
                searchBoxButton: "win-searchbox-button",
                searchBoxFlyout: "win-searchbox-flyout",
                searchBoxSuggestionResult: "win-searchbox-suggestion-result",
                searchBoxSuggestionQuery: "win-searchbox-suggestion-query",
                searchBoxSuggestionSeparator: "win-searchbox-suggestion-separator",
                searchBoxSuggestionSelected: "win-searchbox-suggestion-selected",
                searchBoxFlyoutHighlightText: "win-searchbox-flyout-highlighttext",
                searchBoxButtonInputFocus: "win-searchbox-button-input-focus",
                searchBoxInputFocus: "win-searchbox-input-focus",
                searchBoxSuggestionResultText: "win-searchbox-suggestion-result-text",
                searchBoxSuggestionResultDetailedText: "win-searchbox-suggestion-result-detailed-text",
                searchboxDisabled: "win-searchbox-disabled",
                searchboxHitHighlightSpan: "win-searchbox-hithighlight-span",
            };

            var EventName = {
                querychanged: "querychanged",
                querysubmitted: "querysubmitted",
                resultsuggestionchosen: "resultsuggestionchosen",
                suggestionsrequested: "suggestionsrequested",
                receivingfocusonkeyboardinput: "receivingfocusonkeyboardinput"
            };

            var strings = {
                get duplicateConstruction() { return "Invalid argument: Controls may only be instantiated one time for each DOM element"; },
                get invalidSearchBoxSuggestionKind() { return "Error: Invalid search suggestion kind."; },
                get ariaLabel() { return _Resources._getWinJSString("ui/searchBoxAriaLabel").value; },
                get ariaLabelInputNoPlaceHolder() { return _Resources._getWinJSString("ui/searchBoxAriaLabelInputNoPlaceHolder").value; },
                get ariaLabelInputPlaceHolder() { return _Resources._getWinJSString("ui/searchBoxAriaLabelInputPlaceHolder").value; },
                get ariaLabelButton() { return _Resources._getWinJSString("ui/searchBoxAriaLabelButton").value; },
                get ariaLabelQuery() { return _Resources._getWinJSString("ui/searchBoxAriaLabelQuery").value; },
                get ariaLabelSeparator() { return _Resources._getWinJSString("ui/searchBoxAriaLabelSeparator").value; },
                get ariaLabelResult() { return _Resources._getWinJSString("ui/searchBoxAriaLabelResult").value; }
            };

            var SearchBox = _Base.Class.define(function SearchBox_ctor(element, options) {
                /// <signature helpKeyword="WinJS.UI.SearchBox.SearchBox">
                /// <summary locid="WinJS.UI.SearchBox.constructor">
                /// Creates a new SearchBox.
                /// </summary>
                /// <param name="element" domElement="true" locid="WinJS.UI.SearchBox.constructor_p:element">
                /// The DOM element that hosts the SearchBox.
                /// </param>
                /// <param name="options" type="Object" locid="WinJS.UI.SearchControl.constructor_p:options">
                /// An object that contains one or more property/value pairs to apply to the new control.
                /// Each property of the options object corresponds to one of the control's properties or events.
                /// Event names must begin with "on". For example, to provide a handler for the querychanged event,
                /// add a property named "onquerychanged" to the options object and set its value to the event handler.
                /// This parameter is optional.
                /// </param>
                /// <returns type="WinJS.UI.SearchBox" locid="WinJS.UI.SearchBox.constructor_returnValue">
                /// The new SearchBox.
                /// </returns>
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </signature>

                element = element || _Global.document.createElement("div");

                if (element.winControl) {
                    throw new _ErrorFromName("WinJS.UI.SearchBox.DuplicateConstruction", strings.duplicateConstruction);
                }
                element.winControl = this;

                // Elements
                this._domElement = null;
                this._inputElement = null;
                this._buttonElement = null;
                this._flyout = null;
                this._flyoutDivElement = null;
                this._repeaterDivElement = null;
                this._repeater = null;

                // Variables
                this._disposed = false;
                this._focusOnKeyboardInput = false;
                this._chooseSuggestionOnEnter = false;
                this._lastKeyPressLanguage = "";

                // These are used to eliminate redundant query submitted events
                this._prevQueryText = "";
                this._prevLinguisticDetails = this._createSearchQueryLinguisticDetails([], 0, 0, "", "");
                this._prevCompositionStart = 0;
                this._prevCompositionLength = 0;
                this._isProcessingDownKey = false;
                this._isProcessingUpKey = false;
                this._isProcessingTabKey = false;
                this._isProcessingEnterKey = false;
                this._isFlyoutPointerDown = false;
                this._reflowImeOnPointerRelease = false;

                // Focus and selection related variables
                this._currentFocusedIndex = -1;
                this._currentSelectedIndex = -1;

                this._suggestionRendererBind = this._suggestionRenderer.bind(this);
                this._requestingFocusOnKeyboardInputHandlerBind = this._requestingFocusOnKeyboardInputHandler.bind(this);
                this._suggestionsRequestedHandlerBind = this._suggestionsRequestedHandler.bind(this);
                this._suggestionsChangedHandlerBind = this._suggestionsChangedHandler.bind(this);
                this._keydownCaptureHandlerBind = this._keydownCaptureHandler.bind(this);
                this._frameLoadCaptureHandlerBind = this._frameLoadCaptureHandler.bind(this);

                // Find out if we are in local compartment and if search APIs are available.
                this._searchSuggestionManager = null;
                this._searchSuggestions = null;

                // Get the search suggestion provider if it is available
                if (_WinRT.Windows.ApplicationModel.Search.Core.SearchSuggestionManager) {
                    this._searchSuggestionManager = new _WinRT.Windows.ApplicationModel.Search.Core.SearchSuggestionManager();
                } else {
                    this._searchSuggestionManager = new _SearchSuggestionManagerShim._SearchSuggestionManagerShim();
                }
                this._searchSuggestions = this._searchSuggestionManager.suggestions;

                this._hitFinder = null;
                this._setElement(element);
                _Control.setOptions(this, options);
                this._setAccessibilityProperties();
                _ElementUtilities.addClass(element, "win-disposable");
            }, {

                /// <field type='HTMLElement' domElement='true' hidden='true' locid="WinJS.UI.SearchBox.element" helpKeyword="WinJS.UI.SearchBox.element">
                /// The DOM element that hosts the SearchBox.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                element: {
                    get: function () {
                        return this._domElement;
                    }
                },

                /// <field type='String' locid="WinJS.UI.SearchBox.placeholderText" helpKeyword="WinJS.UI.SearchBox.placeholderText">
                /// Gets or sets the placeholder text for the SearchBox. This text is displayed if there is no
                /// other text in the input box.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                placeholderText: {
                    get: function () {
                        return this._inputElement.placeholder;
                    },
                    set: function (value) {
                        this._inputElement.placeholder = value;
                        this._updateInputElementAriaLabel();
                    }
                },

                /// <field type='String' locid="WinJS.UI.SearchBox.queryText" helpKeyword="WinJS.UI.SearchBox.queryText">
                /// Gets or sets the query text for the SearchBox.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                queryText: {
                    get: function () {
                        return this._inputElement.value;
                    },
                    set: function (value) {
                        this._inputElement.value = value;
                    }
                },

                /// <field type='bool' locid="WinJS.UI.SearchBox.searchHistoryDisabled" helpKeyword="WinJS.UI.SearchBox.searchHistoryDisabled">
                /// Gets or sets a value that specifies whether search history is disabled for the SearchBox. The default value is false.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                searchHistoryDisabled: {
                    get: function () {
                        if (this._searchSuggestionManager) {
                            return !this._searchSuggestionManager.searchHistoryEnabled;
                        } else {
                            return true;
                        }
                    },
                    set: function (value) {
                        if (this._searchSuggestionManager) {
                            this._searchSuggestionManager.searchHistoryEnabled = !value;
                        }
                    }
                },

                /// <field type='String' locid="WinJS.UI.SearchBox.searchHistoryContext" helpKeyword="WinJS.UI.SearchBox.searchHistoryContext">
                /// Gets or sets the search history context for the SearchBox. The search history context string is used as a secondary key for storing search history.
                /// (The primary key is the AppId.) An app can use the search history context string to store different search histories based on the context of the application.
                /// If you don't set this property, the system assumes that all searches in your app occur in the same context.
                /// If you update this property while the search pane is open with suggestions showing, the changes won't take effect until the user enters the next character.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                searchHistoryContext: {
                    get: function () {
                        if (this._searchSuggestionManager) {
                            return this._searchSuggestionManager.searchHistoryContext;
                        } else {
                            return "";
                        }
                    },
                    set: function (value) {
                        if (this._searchSuggestionManager) {
                            this._searchSuggestionManager.searchHistoryContext = value;
                        }
                    }
                },

                /// <field type='String' locid="WinJS.UI.SearchBox.focusOnKeyboardInput" helpKeyword="WinJS.UI.SearchBox.focusOnKeyboardInput">
                /// Enable automatically focusing the search box when the user types into the app window (off by default) While this is enabled,
                /// input on the current thread will be intercepted and redirected to the search box. Only textual input will trigger the search box to focus.
                /// The caller will continue to receive non-text keys (such as arrows, tab, etc
                /// This will also not affect WIN/CTRL/ALT key combinations (except for Ctrl-V for paste).
                /// If the client needs more to happen than just set focus in the box (make control visible, etc.), they will need to handle the event.
                /// If enabled, the app must be sure to disable this if the user puts focus in some other edit field.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                focusOnKeyboardInput: {
                    get: function () {
                        return this._focusOnKeyboardInput;
                    },
                    set: function (value) {
                        if (this._focusOnKeyboardInput && !value) {
                            if (!(this._searchSuggestionManager instanceof _SearchSuggestionManagerShim._SearchSuggestionManagerShim)) {
                                this._searchSuggestionManager.removeEventListener("requestingfocusonkeyboardinput", this._requestingFocusOnKeyboardInputHandlerBind);
                            } else {
                                this._updateKeydownCaptureListeners(_Global.top, false /*add*/);
                            }

                        } else if (!this._focusOnKeyboardInput && !!value) {
                            if (!(this._searchSuggestionManager instanceof _SearchSuggestionManagerShim._SearchSuggestionManagerShim)) {
                                this._searchSuggestionManager.addEventListener("requestingfocusonkeyboardinput", this._requestingFocusOnKeyboardInputHandlerBind);
                            } else {
                                this._updateKeydownCaptureListeners(_Global.top, true /*add*/);
                            }

                        }
                        this._focusOnKeyboardInput = !!value;
                    }
                },

                /// <field type='String' locid="WinJS.UI.SearchBox.chooseSuggestionOnEnter" helpKeyword="WinJS.UI.SearchBox.chooseSuggestionOnEnter">
                /// Gets or sets whether the first suggestion is chosen when the user presses Enter.
                /// When set to true, as the user types in the search box, a focus rectangle is drawn on the first search suggestion
                /// (if present and no IME composition in progress).  Pressing enter will behave the same as if clicked on the focused suggestion,
                /// and the down arrow key press will put real focus to the second suggestion and the up arrow key will remove focus.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                chooseSuggestionOnEnter: {
                    get: function () {
                        return this._chooseSuggestionOnEnter;
                    },
                    set: function (value) {
                        this._chooseSuggestionOnEnter = !!value;
                        this._updateSearchButtonClass();
                    }
                },

                /// <field type='bool' locid="WinJS.UI.SearchBox.disabled" helpKeyword="WinJS.UI.SearchBox.disabled">
                /// Gets or sets a value that specifies whether the SearchBox is disabled.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                disabled: {
                    get: function () {
                        return this._inputElement.disabled;
                    },
                    set: function (value) {
                        if (this._inputElement.disabled === !!value) {
                            return;
                        }

                        if (!value) {
                            // Enable control
                            this._inputElement.disabled = false;
                            this._buttonElement.disabled = false;
                            this._domElement.disabled = false;
                            _ElementUtilities.removeClass(this.element, ClassName.searchboxDisabled);
                            if (_Global.document.activeElement === this.element) {
                                _ElementUtilities._setActive(this._inputElement);
                            }
                        } else {
                            // Disable control
                            if (this._isFlyoutShown) {
                                this._hideFlyout();
                            }
                            _ElementUtilities.addClass(this.element, ClassName.searchboxDisabled);
                            this._inputElement.disabled = true;
                            this._buttonElement.disabled = true;
                            this._domElement.disabled = true;
                        }
                    }
                },

                // Methods
                setLocalContentSuggestionSettings: function SearchBox_setLocalContentSuggestionSettings(settings) {
                    /// <signature helpKeyword="WinJS.UI.SearchBox.SetLocalContentSuggestionSettings">
                    /// <summary locid="WinJS.UI.SearchBox.SetLocalContentSuggestionSettings">
                    /// Specifies whether suggestions based on local files are automatically displayed in the search pane, and defines the criteria that
                    /// the system uses to locate and filter these suggestions.
                    /// </summary>
                    /// <param name="eventName" type="Windows.ApplicationModel.Search. LocalContentSuggestionSettings" locid="WinJS.UI.SearchBox.setLocalContentSuggestionSettings_p:settings">
                    /// The new settings for local content suggestions.
                    /// </param>
                    /// <compatibleWith platform="Windows" minVersion="8.1"/>
                    /// </signature>
                    if (this._searchSuggestionManager) {
                        this._searchSuggestionManager.setLocalContentSuggestionSettings(settings);
                    }
                },

                dispose: function SearchBox() {
                    /// <signature helpKeyword="WinJS.UI.SearchBox.dispose">
                    /// <summary locid="WinJS.UI.SearchBox.dispose">
                    /// Disposes this control.
                    /// </summary>
                    /// <compatibleWith platform="Windows" minVersion="8.1"/>
                    /// </signature>
                    if (this._disposed) {
                        return;
                    }

                    // Cancel pending promises.
                    if (this._flyoutOpenPromise) {
                        this._flyoutOpenPromise.cancel();
                    }

                    // Detach winrt events.
                    if (this._focusOnKeyboardInput) {
                        if (!(this._searchSuggestionManager instanceof _SearchSuggestionManagerShim._SearchSuggestionManagerShim)) {
                            this._searchSuggestionManager.removeEventListener("requestingfocusonkeyboardinput", this._requestingFocusOnKeyboardInputHandlerBind);
                        } else {
                            this._updateKeydownCaptureListeners(_Global.top, false /*add*/);
                        }

                    }
                    this._searchSuggestions.removeEventListener("vectorchanged", this._suggestionsChangedHandlerBind);
                    this._searchSuggestionManager.removeEventListener("suggestionsrequested", this._suggestionsRequestedHandlerBind);

                    this._searchSuggestionManager = null;
                    this._searchSuggestions = null;
                    this._hitFinder = null;

                    this._disposed = true;

                },

                /// <field type="Function" locid="WinJS.UI.SearchBox.onquerychanged" helpKeyword="WinJS.UI.SearchBox.onquerychanged">
                /// Raised when user or app changes the query text.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                onquerychanged: createEvent(EventName.querychanged),

                /// <field type="Function" locid="WinJS.UI.SearchBox.onquerysubmitted" helpKeyword="WinJS.UI.SearchBox.onquerysubmitted">
                /// Raised when user clicks on search glyph or presses enter button.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                onquerysubmitted: createEvent(EventName.querysubmitted),

                /// <field type="Function" locid="WinJS.UI.SearchBox.onresultsuggestionchosen" helpKeyword="WinJS.UI.SearchBox.onresultsuggestionchosen">
                /// Raised when user clicks on one of the suggestions displayed.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                onresultsuggestionchosen: createEvent(EventName.resultsuggestionchosen),

                /// <field type="Function" locid="WinJS.UI.SearchBox.onsuggestionsrequested" helpKeyword="WinJS.UI.SearchBox.onsuggestionsrequested">
                /// Raised when Windows requests search suggestions from the app.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                onsuggestionsrequested: createEvent(EventName.suggestionsrequested),

                // Private methods
                _isFlyoutShown: function SearchBox_isFlyoutShown() {
                    return (this._flyoutDivElement.style.display !== "none");
                },

                _isFlyoutBelow: function SearchBox_isFlyoutBelow() {
                    if (this._flyoutDivElement.getBoundingClientRect().top > this._inputElement.getBoundingClientRect().top) {
                        return true;
                    }
                    return false;
                },

                _getFlyoutTop: function SearchBox_getFlyoutTop() {
                    if (this._isFlyoutBelow()) {
                        return this._inputElement.getBoundingClientRect().bottom;
                    }
                    var popupHeight = this._flyoutDivElement.getBoundingClientRect().bottom - this._flyoutDivElement.getBoundingClientRect().top;
                    return this._inputElement.getBoundingClientRect().top - popupHeight;
                },

                _getFlyoutBottom: function SearchBox_getFlyoutBottom() {
                    if (this._isFlyoutBelow()) {
                        var popupHeight = this._flyoutDivElement.getBoundingClientRect().bottom - this._flyoutDivElement.getBoundingClientRect().top;
                        return this._inputElement.getBoundingClientRect().bottom + popupHeight;
                    }
                    return this._inputElement.getBoundingClientRect().top;
                },

                _updateFlyoutTopAndTouchAction: function SearchBox_updateFlyoutTopAndTouchAction() {
                    var popupHeight = this._flyoutDivElement.getBoundingClientRect().bottom - this._flyoutDivElement.getBoundingClientRect().top;
                    if (!this._isFlyoutBelow()) {
                        this._flyoutDivElement.style.top = "-" + popupHeight + "px";
                    }

                    // ms-scroll-chaining:none will still chain scroll parent element if child div does
                    // not have a scroll bar. Prevent this by setting and updating touch action
                    if (this._flyoutDivElement.scrollHeight > popupHeight) {
                        this._flyoutDivElement.style.touchAction = "pan-y";
                    } else {
                        this._flyoutDivElement.style.touchAction = "none";
                    }
                },

                _showFlyout: function SearchBox_showFlyout() {

                    if (this._isFlyoutShown()) {
                        return;
                    }

                    if (this._suggestionsData.length === 0) {
                        return;
                    }

                    this._flyoutDivElement.style.display = "block";

                    // Display above vs below
                    var minPopupHeight = this._flyoutDivElement.clientHeight;
                    if (minPopupHeight < SearchBox._Constants.MIN_POPUP_HEIGHT) {
                        minPopupHeight = SearchBox._Constants.MIN_POPUP_HEIGHT;
                    }
                    var flyoutRect = this._flyoutDivElement.getBoundingClientRect();
                    var searchBoxRect = this.element.getBoundingClientRect();
                    var popupHeight = flyoutRect.bottom - flyoutRect.top;
                    var popupWidth = flyoutRect.right - flyoutRect.left;
                    var searchBoxWidth = searchBoxRect.right - searchBoxRect.left;
                    var documentClientHeight = _Global.document.documentElement.clientHeight;
                    var documentClientWidth = _Global.document.documentElement.clientWidth;
                    var searchBoxClientHeight = this.element.clientHeight;
                    var searchBoxClientLeft = this.element.clientLeft;

                    var flyoutBelowSearchBox = true;
                    if ((searchBoxRect.bottom + minPopupHeight) <= documentClientHeight) {
                        // There is enough space below. Show below
                        this._flyoutDivElement.style.top = searchBoxClientHeight + "px";
                    } else if ((searchBoxRect.top - minPopupHeight) >= 0) {
                        // There is enough space above. Show above
                        this._flyoutDivElement.style.top = "-" + popupHeight + "px";
                        flyoutBelowSearchBox = false;
                    } else {
                        // Not enough space above or below. Show below.
                        this._flyoutDivElement.style.top = searchBoxClientHeight + "px";
                    }

                    // Align left vs right edge
                    var alignRight;
                    if (_Global.getComputedStyle(this._flyoutDivElement).direction === "rtl") {
                        // RTL: Align to the right edge if there is enough space to the left of the search box's
                        // right edge, or if there is not enough space to fit the flyout aligned to either edge.
                        alignRight = ((searchBoxRect.right - popupWidth) >= 0) || ((searchBoxRect.left + popupWidth) > documentClientWidth);

                    } else {
                        // LTR: Align to the right edge if there isn't enough space to the right of the search box's
                        // left edge, but there is enough space to the left of the search box's right edge.
                        alignRight = ((searchBoxRect.left + popupWidth) > documentClientWidth) && ((searchBoxRect.right - popupWidth) >= 0);
                    }

                    if (alignRight) {
                        this._flyoutDivElement.style.left = (searchBoxWidth - popupWidth - searchBoxClientLeft) + "px";
                    } else {
                        this._flyoutDivElement.style.left = "-" + searchBoxClientLeft + "px";
                    }

                    // ms-scroll-chaining:none will still chain scroll parent element if child div does
                    // not have a scroll bar. Prevent this by setting and updating touch action
                    if (this._flyoutDivElement.scrollHeight > popupHeight) {
                        this._flyoutDivElement.style.touchAction = "pan-y";
                    } else {
                        this._flyoutDivElement.style.touchAction = "none";
                    }

                    this._addFlyoutIMEPaddingIfRequired();

                    if (this._flyoutOpenPromise) {
                        this._flyoutOpenPromise.cancel();
                        this._flyoutOpenPromise = null;
                    }
                    var animationKeyframe = flyoutBelowSearchBox ? "WinJS-flyoutBelowSearchBox-showPopup" : "WinJS-flyoutAboveSearchBox-showPopup";
                    this._flyoutOpenPromise = Animations.showPopup(this._flyoutDivElement, { top: "0px", left: "0px", keyframe: animationKeyframe });
                },

                _hideFlyout: function SearchBox_hideFlyout() {
                    if (this._isFlyoutShown()) {
                        this._flyoutDivElement.style.display = "none";
                        this._updateSearchButtonClass();
                    }
                },

                _addNewSpan: function SearchBox_addNewSpan(element, textContent, insertBefore) {
                    // Adds new span element with specified inner text as child to element, placed before insertBefore
                    var spanElement = _Global.document.createElement("span");
                    spanElement.textContent = textContent;
                    spanElement.setAttribute("aria-hidden", "true");
                    _ElementUtilities.addClass(spanElement, ClassName.searchboxHitHighlightSpan);
                    element.insertBefore(spanElement, insertBefore);
                    return spanElement;
                },

                _addHitHighlightedText: function SearchBox_addHitHighlightedText(element, item, text) {
                    if (text) {
                        // Remove any existing hit highlighted text spans
                        _ElementListUtilities.query("." + ClassName.searchboxHitHighlightSpan, element).forEach(function (childElement) {
                            childElement.parentNode.removeChild(childElement);
                        });

                        // Insert spans at the front of element
                        var firstChild = element.firstChild;

                        var hitsProvided = item.hits;
                        if ((!hitsProvided) && (this._hitFinder !== null) && (item.kind !== _SearchSuggestionManagerShim._SearchSuggestionKind.Separator)) {
                            hitsProvided = this._hitFinder.find(text);
                        }

                        var hits = SearchBox._sortAndMergeHits(hitsProvided);

                        var lastPosition = 0;
                        for (var i = 0; i < hits.length; i++) {
                            var hit = hits[i];

                            // Add previous normal text
                            this._addNewSpan(element, text.substring(lastPosition, hit.startPosition), firstChild);

                            lastPosition = hit.startPosition + hit.length;

                            // Add hit highlighted text
                            var spanHitHighlightedText = this._addNewSpan(element, text.substring(hit.startPosition, lastPosition), firstChild);
                            _ElementUtilities.addClass(spanHitHighlightedText, ClassName.searchBoxFlyoutHighlightText);
                        }

                        // Add final normal text
                        if (lastPosition < text.length) {
                            this._addNewSpan(element, text.substring(lastPosition), firstChild);
                        }
                    }
                },

                _findSuggestionElementIndex: function SearchBox_findSuggestionElementIndex(curElement) {
                    if (curElement) {
                        for (var i = 0; i < this._suggestionsData.length; i++) {
                            if (this._repeater.elementFromIndex(i) === curElement) {
                                return i;
                            }
                        }
                    }
                    return -1;
                },

                _isSuggestionSelectable: function SearchBox_isSuggestionSelectable(suggestion) {
                    return ((suggestion.kind === _SearchSuggestionManagerShim._SearchSuggestionKind.Query) ||
                            (suggestion.kind === _SearchSuggestionManagerShim._SearchSuggestionKind.Result));
                },

                _findNextSuggestionElementIndex: function SearchBox_findNextSuggestionElementIndex(curIndex) {
                    // Returns -1 if there are no focusable elements after curIndex
                    // Returns first element if curIndex < 0
                    var startIndex = curIndex + 1;
                    if (startIndex < 0) {
                        startIndex = 0;
                    }

                    for (var i = startIndex; i < this._suggestionsData.length; i++) {
                        if ((this._repeater.elementFromIndex(i)) && (this._isSuggestionSelectable(this._suggestionsData.getAt(i)))) {
                            return i;
                        }
                    }
                    return -1;
                },

                _findPreviousSuggestionElementIndex: function SearchBox_findPreviousSuggestionElementIndex(curIndex) {
                    // Returns -1 if there are no focusable elements before curIndex
                    // Returns last element if curIndex >= suggestionsdata.length
                    var startIndex = curIndex - 1;
                    if (startIndex >= this._suggestionsData.length) {
                        startIndex = this._suggestionsData.length - 1;
                    }

                    for (var i = startIndex; i >= 0; i--) {
                        if ((this._repeater.elementFromIndex(i)) && (this._isSuggestionSelectable(this._suggestionsData.getAt(i)))) {
                            return i;
                        }
                    }
                    return -1;
                },

                _trySetFocusOnSuggestionIndex: function SearchBox_trySetFocusOnSuggestionIndex(index) {
                    try {
                        this._repeater.elementFromIndex(index).focus();
                    } catch (e) {
                    }
                },

                _updateFakeFocus: function SearchBox_updateFakeFocus() {
                    var firstElementIndex;
                    if (this._isFlyoutShown() && (this._chooseSuggestionOnEnter)) {
                        firstElementIndex = this._findNextSuggestionElementIndex(-1);
                    } else {
                        // This will clear the fake focus.
                        firstElementIndex = -1;
                    }

                    this._selectSuggestionAtIndex(firstElementIndex);
                },

                _updateSearchButtonClass: function SearchBox_updateSearchButtonClass() {
                    if ((this._currentSelectedIndex !== -1) || (_Global.document.activeElement !== this._inputElement)) {
                        // Focus is not in input. remove class
                        _ElementUtilities.removeClass(this._buttonElement, ClassName.searchBoxButtonInputFocus);
                    } else if (_Global.document.activeElement === this._inputElement) {
                        _ElementUtilities.addClass(this._buttonElement, ClassName.searchBoxButtonInputFocus);
                    }
                },

                _selectSuggestionAtIndex: function SearchBox_selectSuggestionAtIndex(indexToSelect) {
                    // Sets focus on the specified element and removes focus from others.
                    // Clears selection if index is outside of suggestiondata index range.
                    var curElement = null;
                    for (var i = 0; i < this._suggestionsData.length; i++) {
                        curElement = this._repeater.elementFromIndex(i);
                        if (i !== indexToSelect) {
                            _ElementUtilities.removeClass(curElement, ClassName.searchBoxSuggestionSelected);
                            curElement.setAttribute("aria-selected", "false");
                        } else {
                            _ElementUtilities.addClass(curElement, ClassName.searchBoxSuggestionSelected);
                            this._scrollToView(curElement);
                            curElement.setAttribute("aria-selected", "true");
                        }
                    }
                    this._updateSearchButtonClass();
                    this._currentSelectedIndex = indexToSelect;
                    if (curElement) {
                        this._inputElement.setAttribute("aria-activedescendant", this._repeaterDivElement.id + indexToSelect);
                    } else if (this._inputElement.hasAttribute("aria-activedescendant")) {
                        this._inputElement.removeAttribute("aria-activedescendant");
                    }
                },

                _scrollToView: function SearchBox_scrollToView(targetElement) {
                    var popupHeight = this._flyoutDivElement.getBoundingClientRect().bottom - this._flyoutDivElement.getBoundingClientRect().top;
                    if ((targetElement.offsetTop + targetElement.offsetHeight) > (this._flyoutDivElement.scrollTop + popupHeight)) {
                        // Element to scroll is below popup visible area
                        var scrollDifference = (targetElement.offsetTop + targetElement.offsetHeight) - (this._flyoutDivElement.scrollTop + popupHeight);
                        _ElementUtilities._zoomTo(this._flyoutDivElement, { contentX: 0, contentY: (this._flyoutDivElement.scrollTop + scrollDifference), viewportX: 0, viewportY: 0 });
                    } else if (targetElement.offsetTop < this._flyoutDivElement.scrollTop) {
                        // Element to scroll is above popup visible area
                        _ElementUtilities._zoomTo(this._flyoutDivElement, { contentX: 0, contentY: targetElement.offsetTop, viewportX: 0, viewportY: 0 });
                    }
                },

                _querySuggestionRenderer: function SearchBox_querySuggestionRenderer(item) {
                    var root = _Global.document.createElement("div");

                    this._addHitHighlightedText(root, item, item.text);
                    root.title = item.text;

                    _ElementUtilities.addClass(root, ClassName.searchBoxSuggestionQuery);

                    var that = this;
                    _ElementUtilities._addEventListener(root, "pointerup", function (ev) {
                        that._inputElement.focus();
                        that._processSuggestionChosen(item, ev);
                    });

                    root.setAttribute("role", "option");
                    var ariaLabel = _Resources._formatString(strings.ariaLabelQuery, item.text);
                    root.setAttribute("aria-label", ariaLabel);
                    return root;
                },

                _separatorSuggestionRenderer: function SearchBox_separatorSuggestionRenderer(item) {
                    var root = _Global.document.createElement("div");
                    if (item.text.length > 0) {
                        var textElement = _Global.document.createElement("div");
                        textElement.textContent = item.text;
                        textElement.title = item.text;
                        textElement.setAttribute("aria-hidden", "true");
                        root.appendChild(textElement);
                    }
                    root.insertAdjacentHTML("beforeend", "<hr/>");
                    _ElementUtilities.addClass(root, ClassName.searchBoxSuggestionSeparator);
                    root.setAttribute("role", "separator");
                    var ariaLabel = _Resources._formatString(strings.ariaLabelSeparator, item.text);
                    root.setAttribute("aria-label", ariaLabel);
                    return root;
                },

                _resultSuggestionRenderer: function SearchBox_resultSuggestionRenderer(item) {
                    var root = _Global.document.createElement("div");
                    var image = new _Global.Image();
                    image.style.opacity = 0;
                    var loadImage = function (url) {
                        function onload() {
                            image.removeEventListener("load", onload, false);
                            Animations.fadeIn(image);
                        }
                        image.addEventListener("load", onload, false);
                        image.src = url;
                    };

                    if (item.image !== null) {
                        item.image.openReadAsync().then(function (streamWithContentType) {
                            if (streamWithContentType !== null) {
                                loadImage(_Global.URL.createObjectURL(streamWithContentType, { oneTimeOnly: true }));
                            }
                        });
                    } else if (item.imageUrl !== null) {
                        loadImage(item.imageUrl);
                    }
                    image.setAttribute("aria-hidden", "true");
                    root.appendChild(image);

                    var divElement = _Global.document.createElement("div");
                    _ElementUtilities.addClass(divElement, ClassName.searchBoxSuggestionResultText);
                    this._addHitHighlightedText(divElement, item, item.text);
                    divElement.title = item.text;
                    divElement.setAttribute("aria-hidden", "true");
                    root.appendChild(divElement);

                    var brElement = _Global.document.createElement("br");
                    divElement.appendChild(brElement);

                    var divDetailElement = _Global.document.createElement("span");
                    _ElementUtilities.addClass(divDetailElement, ClassName.searchBoxSuggestionResultDetailedText);
                    this._addHitHighlightedText(divDetailElement, item, item.detailText);
                    divDetailElement.title = item.detailText;
                    divDetailElement.setAttribute("aria-hidden", "true");
                    divElement.appendChild(divDetailElement);

                    _ElementUtilities.addClass(root, ClassName.searchBoxSuggestionResult);

                    var that = this;
                    _ElementUtilities._addEventListener(root, "pointerup", function (ev) {
                        that._inputElement.focus();
                        that._processSuggestionChosen(item, ev);
                    });

                    root.setAttribute("role", "option");
                    var ariaLabel = _Resources._formatString(strings.ariaLabelResult, item.text, item.detailText);
                    root.setAttribute("aria-label", ariaLabel);
                    return root;
                },

                _suggestionRenderer: function SearchBox_suggestionRenderer(item) {
                    var root = null;
                    if (!item) {
                        return root;
                    }
                    if (item.kind === _SearchSuggestionManagerShim._SearchSuggestionKind.Query) {
                        root = this._querySuggestionRenderer(item);
                    } else if (item.kind === _SearchSuggestionManagerShim._SearchSuggestionKind.Separator) {
                        root = this._separatorSuggestionRenderer(item);
                    } else if (item.kind === _SearchSuggestionManagerShim._SearchSuggestionKind.Result) {
                        root = this._resultSuggestionRenderer(item);
                    } else {
                        throw new _ErrorFromName("WinJS.UI.SearchBox.invalidSearchBoxSuggestionKind", strings.invalidSearchBoxSuggestionKind);
                    }

                    return root;
                },

                _setElement: function SearchBox_setElement(element) {
                    this._domElement = element;
                    _ElementUtilities.addClass(this._domElement, ClassName.searchBox);

                    this._inputElement = _Global.document.createElement("input");
                    this._inputElement.type = "search";
                    _ElementUtilities.addClass(this._inputElement, ClassName.searchBoxInput);

                    this._buttonElement = _Global.document.createElement("div");
                    this._buttonElement.tabIndex = -1;
                    _ElementUtilities.addClass(this._buttonElement, ClassName.searchBoxButton);

                    this._flyoutDivElement = _Global.document.createElement('div');
                    _ElementUtilities.addClass(this._flyoutDivElement, ClassName.searchBoxFlyout);

                    this._repeaterDivElement = _Global.document.createElement('div');
                    this._suggestionsData = new BindingList.List();
                    this._repeater = new Repeater.Repeater(this._repeaterDivElement, { data: this._suggestionsData, template: this._suggestionRendererBind });

                    this._domElement.appendChild(this._inputElement);
                    this._domElement.appendChild(this._buttonElement);
                    this._domElement.appendChild(this._flyoutDivElement);
                    this._flyoutDivElement.appendChild(this._repeaterDivElement);
                    this._hideFlyout();

                    this._wireupUserEvents();
                    this._wireupWinRTEvents();
                    this._wireupRepeaterEvents();
                },

                _setAccessibilityProperties: function Searchbox_setAccessibilityProperties() {
                    // Set up accessibility properties
                    var label = this._domElement.getAttribute("aria-label");
                    if (!label) {
                        this._domElement.setAttribute("aria-label", strings.ariaLabel);
                    }
                    this._domElement.setAttribute("role", "group");
                    this._updateInputElementAriaLabel();
                    this._inputElement.setAttribute("role", "textbox");
                    this._buttonElement.setAttribute("role", "button");
                    this._buttonElement.setAttribute("aria-label", strings.ariaLabelButton);
                    this._repeaterDivElement.setAttribute("role", "listbox");
                    _ElementUtilities._ensureId(this._repeaterDivElement);
                    this._inputElement.setAttribute("aria-controls", this._repeaterDivElement.id);
                    this._repeaterDivElement.setAttribute("aria-live", "polite");
                },

                _updateInputElementAriaLabel: function Searchbox_updateInputElementAriaLabel() {
                    var ariaLabel = strings.ariaLabelInputNoPlaceHolder;
                    if (this._inputElement.placeholder && this._inputElement.placeholder) {
                        ariaLabel = _Resources._formatString(strings.ariaLabelInputPlaceHolder, this._inputElement.placeholder);
                    }
                    this._inputElement.setAttribute("aria-label", ariaLabel);
                },

                _submitQuery: function Searchbox_submitQuery(queryText, fillLinguisticDetails, event) {
                    if (this._disposed) {
                        return;
                    }

                    // get the most up to date value of the input langauge from WinRT if available
                    if (_WinRT.Windows.Globalization.Language) {
                        this._lastKeyPressLanguage = _WinRT.Windows.Globalization.Language.currentInputMethodLanguageTag;
                    }

                    this._fireEvent(SearchBox._EventName.querysubmitted, {
                        language: this._lastKeyPressLanguage,
                        linguisticDetails: this._getLinguisticDetails(true /*useCache*/, fillLinguisticDetails), // allow caching, but generate empty linguistic details if suggestion is used
                        queryText: queryText,
                        keyModifiers: SearchBox._getKeyModifiers(event)
                    });

                    if (this._searchSuggestionManager) {
                        this._searchSuggestionManager.addToHistory(
                            this._inputElement.value,
                            this._lastKeyPressLanguage
                            );
                    }
                },

                _processSuggestionChosen: function Searchbox_processSuggestionChosen(item, event) {
                    this.queryText = item.text;
                    if (item.kind === _SearchSuggestionManagerShim._SearchSuggestionKind.Query) {
                        this._submitQuery(item.text, false /*fillLinguisticDetails*/, event); // force empty linguistic details since explicitly chosen suggestion from list
                    } else if (item.kind === _SearchSuggestionManagerShim._SearchSuggestionKind.Result) {
                        this._fireEvent(SearchBox._EventName.resultsuggestionchosen, {
                            tag: item.tag,
                            keyModifiers: SearchBox._getKeyModifiers(event),
                            storageFile: null
                        });
                    }
                    this._hideFlyout();
                },

                _buttonClickHandler: function SearchBox_buttonClickHandler(event) {
                    this._inputElement.focus();
                    this._submitQuery(this._inputElement.value, true /*fillLinguisticDetails*/, event);
                    this._hideFlyout();
                },

                _inputOrImeChangeHandler: function SearchBox_inputImeChangeHandler() {
                    var isButtonDown = _ElementUtilities._matchesSelector(this._buttonElement, ":active");
                    // swallow the IME change event that gets fired when composition is ended due to keyboarding down to the suggestion list & mouse down on the button
                    if (!this._isProcessingImeFocusLossKey() && !isButtonDown && !this._isFlyoutPointerDown) {
                        var linguisticDetails = this._getLinguisticDetails(false /*useCache*/, true /*createFilled*/); // never cache on explicit user changes
                        var hasLinguisticDetailsChanged = this._hasLinguisticDetailsChanged(linguisticDetails); // updates this._prevLinguisticDetails

                        // Keep the previous composition cache up to date, execpt when composition ended with no text change and alternatives are kept.
                        // In that case, we need to use the cached values to correctly generate the query prefix/suffix for substituting alternatives, but still report to the client that the composition has ended (via start & length of composition of 0)
                        if ((this._inputElement.value !== this._prevQueryText) || (this._prevCompositionLength === 0) || (linguisticDetails.queryTextCompositionLength > 0)) {
                            this._prevCompositionStart = linguisticDetails.queryTextCompositionStart;
                            this._prevCompositionLength = linguisticDetails.queryTextCompositionLength;
                        }

                        if ((this._prevQueryText === this._inputElement.value) && !hasLinguisticDetailsChanged) {
                            // Sometimes the input change is fired even if there is no change in input.
                            // Swallow event in those cases.
                            return;
                        }
                        this._prevQueryText = this._inputElement.value;

                        // get the most up to date value of the input langauge from WinRT if available
                        if (_WinRT.Windows.Globalization.Language) {
                            this._lastKeyPressLanguage = _WinRT.Windows.Globalization.Language.currentInputMethodLanguageTag;
                        }

                        if (_WinRT.Windows.Data.Text.SemanticTextQuery) {
                            if (this._inputElement.value !== "") {
                                this._hitFinder = new _WinRT.Windows.Data.Text.SemanticTextQuery(this._inputElement.value, this._lastKeyPressLanguage);
                            } else {
                                this._hitFinder = null;
                            }
                        }

                        this._fireEvent(SearchBox._EventName.querychanged, {
                            language: this._lastKeyPressLanguage,
                            queryText: this._inputElement.value,
                            linguisticDetails: linguisticDetails
                        });
                        if (this._searchSuggestionManager) {
                            this._searchSuggestionManager.setQuery(
                                this._inputElement.value,
                                this._lastKeyPressLanguage,
                                linguisticDetails
                                );
                        }
                    }
                },

                _createSearchQueryLinguisticDetails: function SearchBox_createSearchQueryLinguisticDetails(compositionAlternatives, compositionStartOffset, compositionLength, queryTextPrefix, queryTextSuffix) {
                    var linguisticDetails = null;

                    // The linguistic alternatives we receive are only for the composition string being composed. We need to provide the linguistic alternatives
                    // in the form of the full query text with alternatives embedded.
                    var fullCompositionAlternatives = [];
                    for (var i = 0; i < compositionAlternatives.length; i++) {
                        fullCompositionAlternatives[i] = queryTextPrefix + compositionAlternatives[i] + queryTextSuffix;
                    }

                    if (_WinRT.Windows.ApplicationModel.Search.SearchQueryLinguisticDetails) {
                        linguisticDetails = new _WinRT.Windows.ApplicationModel.Search.SearchQueryLinguisticDetails(fullCompositionAlternatives, compositionStartOffset, compositionLength);
                    } else {
                        // If we're in web compartment, create a script version of the WinRT SearchQueryLinguisticDetails object
                        linguisticDetails = {
                            queryTextAlternatives: fullCompositionAlternatives,
                            queryTextCompositionStart: compositionStartOffset,
                            queryTextCompositionLength: compositionLength
                        };
                    }
                    return linguisticDetails;
                },

                _getLinguisticDetails: function SearchBox_getLinguisticDetails(useCache, createFilled) { // createFilled=false always creates an empty linguistic details object, otherwise generate it or use the cache
                    var linguisticDetails = null;
                    if ((this._inputElement.value === this._prevQueryText) && useCache && this._prevLinguisticDetails && createFilled) {
                        linguisticDetails = this._prevLinguisticDetails;
                    } else {
                        var compositionAlternatives = [];
                        var compositionStartOffset = 0;
                        var compositionLength = 0;
                        var queryTextPrefix = "";
                        var queryTextSuffix = "";
                        if (createFilled && this._inputElement.msGetInputContext && this._inputElement.msGetInputContext().getCompositionAlternatives) {
                            var context = this._inputElement.msGetInputContext();
                            compositionAlternatives = context.getCompositionAlternatives();
                            compositionStartOffset = context.compositionStartOffset;
                            compositionLength = context.compositionEndOffset - context.compositionStartOffset;

                            if ((this._inputElement.value !== this._prevQueryText) || (this._prevCompositionLength === 0) || (compositionLength > 0)) {
                                queryTextPrefix = this._inputElement.value.substring(0, compositionStartOffset);
                                queryTextSuffix = this._inputElement.value.substring(compositionStartOffset + compositionLength);
                            } else {
                                // composition ended, but alternatives have been kept, need to reuse the previous query prefix/suffix, but still report to the client that the composition has ended (start & length of composition of 0)
                                queryTextPrefix = this._inputElement.value.substring(0, this._prevCompositionStart);
                                queryTextSuffix = this._inputElement.value.substring(this._prevCompositionStart + this._prevCompositionLength);
                            }
                        }
                        linguisticDetails = this._createSearchQueryLinguisticDetails(compositionAlternatives, compositionStartOffset, compositionLength, queryTextPrefix, queryTextSuffix);
                    }
                    return linguisticDetails;
                },

                _handleTabKeyDown: function SearchBox_handleTabKeyDown(event) {
                    var closeFlyout = true;
                    if (event.shiftKey) {
                        // If focus is not in input
                        if (this._currentFocusedIndex !== -1) {
                            // Remove selection.
                            this._currentFocusedIndex = -1;
                            this._selectSuggestionAtIndex(this._currentFocusedIndex);
                            this._updateSearchButtonClass();
                            event.preventDefault();
                            event.stopPropagation();
                            closeFlyout = false;
                        }
                    } else if (this._currentFocusedIndex === -1) {
                        if (this._isFlyoutBelow()) {
                            // Move to first element
                            this._currentFocusedIndex = this._findNextSuggestionElementIndex(this._currentFocusedIndex);
                        } else {
                            // Move to last element
                            this._currentFocusedIndex = this._findPreviousSuggestionElementIndex(this._suggestionsData.length);
                        }
                        if (this._currentFocusedIndex !== -1) {
                            this._selectSuggestionAtIndex(this._currentFocusedIndex);
                            this._updateQueryTextWithSuggestionText(this._currentFocusedIndex);
                            this._updateSearchButtonClass();
                            event.preventDefault();
                            event.stopPropagation();
                            closeFlyout = false;
                        }
                    }

                    if (closeFlyout) {
                        this._hideFlyout();
                    }
                },

                _keyDownHandler: function SearchBox_keyDownHandler(event) {
                    this._lastKeyPressLanguage = event.locale;
                    if (event.keyCode === Key.tab) {
                        this._isProcessingTabKey = true;
                    } else if (event.keyCode === Key.upArrow) {
                        this._isProcessingUpKey = true;
                    } else if (event.keyCode === Key.downArrow) {
                        this._isProcessingDownKey = true;
                    } else if ((event.keyCode === Key.enter) && (event.locale === "ko")) {
                        this._isProcessingEnterKey = true;
                    }
                    // Ignore keys handled by ime.
                    if (event.keyCode !== Key.IME) {
                        if (event.keyCode === Key.tab) {
                            this._handleTabKeyDown(event);
                        } else if (event.keyCode === Key.escape) {
                            // If focus is not in input
                            if (this._currentFocusedIndex !== -1) {
                                this.queryText = this._prevQueryText;
                                this._currentFocusedIndex = -1;
                                this._selectSuggestionAtIndex(this._currentFocusedIndex);
                                this._updateSearchButtonClass();
                                event.preventDefault();
                                event.stopPropagation();
                            } else if (this.queryText !== "") {
                                this.queryText = "";
                                this._inputOrImeChangeHandler(null);
                                this._updateSearchButtonClass();
                                event.preventDefault();
                                event.stopPropagation();
                            }
                        } else if (event.keyCode === Key.upArrow) {
                            var prevIndex;
                            if (this._currentSelectedIndex !== -1) {
                                prevIndex = this._findPreviousSuggestionElementIndex(this._currentSelectedIndex);
                                // Restore user entered query when user navigates back to input.
                                if (prevIndex === -1) {
                                    this.queryText = this._prevQueryText;
                                }
                            } else {
                                prevIndex = this._findPreviousSuggestionElementIndex(this._suggestionsData.length);
                            }
                            this._currentFocusedIndex = prevIndex;
                            this._selectSuggestionAtIndex(prevIndex);
                            this._updateQueryTextWithSuggestionText(this._currentFocusedIndex);
                            this._updateSearchButtonClass();
                            event.preventDefault();
                            event.stopPropagation();
                        } else if (event.keyCode === Key.downArrow) {
                            var nextIndex = this._findNextSuggestionElementIndex(this._currentSelectedIndex);
                            // Restore user entered query when user navigates back to input.
                            if ((this._currentSelectedIndex !== -1) && (nextIndex === -1)) {
                                this.queryText = this._prevQueryText;
                            }
                            this._currentFocusedIndex = nextIndex;
                            this._selectSuggestionAtIndex(nextIndex);
                            this._updateQueryTextWithSuggestionText(this._currentFocusedIndex);
                            this._updateSearchButtonClass();
                            event.preventDefault();
                            event.stopPropagation();
                        } else if (event.keyCode === Key.enter) {
                            if (this._currentSelectedIndex === -1) {
                                this._submitQuery(this._inputElement.value, true /*fillLinguisticDetails*/, event);
                            } else {
                                this._processSuggestionChosen(this._suggestionsData.getAt(this._currentSelectedIndex), event);
                            }
                            this._hideFlyout();
                        } else if (SearchBox._isTypeToSearchKey(event)) {
                            // Type to search on suggestions scenario.
                            if (this._currentFocusedIndex !== -1) {
                                this._currentFocusedIndex = -1;
                                this._selectSuggestionAtIndex(-1);
                                this._updateFakeFocus();
                            }
                        }
                    }
                },

                _keyPressHandler: function SearchBox_keyPressHandler(event) {
                    this._lastKeyPressLanguage = event.locale;
                },

                _keyUpHandler: function SearchBox_keyUpHandler(event) {
                    if (event.keyCode === Key.tab) {
                        this._isProcessingTabKey = false;
                    } else if (event.keyCode === Key.upArrow) {
                        this._isProcessingUpKey = false;
                    } else if (event.keyCode === Key.downArrow) {
                        this._isProcessingDownKey = false;
                    } else if (event.keyCode === Key.enter) {
                        this._isProcessingEnterKey = false;
                    }
                },

                _inputFocusHandler: function SearchBox_inputFocusHandler(event) {
                    // Refresh hit highlighting if text has changed since focus was present
                    // This can happen if the user committed a suggestion previously.
                    if (this._inputElement.value !== this._prevQueryText) {
                        if (_WinRT.Windows.Data.Text.SemanticTextQuery) {
                            if (this._inputElement.value !== "") {
                                this._hitFinder = new _WinRT.Windows.Data.Text.SemanticTextQuery(this._inputElement.value, this._inputElement.lang);
                            } else {
                                this._hitFinder = null;
                            }
                        }
                    }

                    // If focus is returning to the input box from outside the search control, show the flyout and refresh the suggestions
                    if ((event.target === this._inputElement) && !this._internalFocusMove) {
                        this._showFlyout();
                        // If focus is not in input
                        if (this._currentFocusedIndex !== -1) {
                            this._selectSuggestionAtIndex(this._currentFocusedIndex);
                        } else {
                            this._updateFakeFocus();
                        }

                        if (this._searchSuggestionManager) {
                            this._searchSuggestionManager.setQuery(
                                this._inputElement.value,
                                this._lastKeyPressLanguage,
                                this._getLinguisticDetails(true /*useCache*/, true /*createFilled*/)
                                );
                        }
                    }

                    this._internalFocusMove = false;
                    _ElementUtilities.addClass(this.element, ClassName.searchBoxInputFocus);
                    this._updateSearchButtonClass();
                },

                _flyoutBlurHandler: function SearchBox_flyoutBlurHandler() {
                    if (this._isElementInSearchControl(_Global.document.activeElement)) {
                        this._internalFocusMove = true;
                    } else {
                        this._hideFlyout();
                        _ElementUtilities.removeClass(this.element, ClassName.searchBoxInputFocus);
                    }
                },

                _inputBlurHandler: function SearchBox_inputBlurHandler(event) {
                    if (!this._isElementInSearchControl(_Global.document.activeElement)) {
                        this._hideFlyout();
                        _ElementUtilities.removeClass(this.element, ClassName.searchBoxInputFocus);
                    }
                    this._updateSearchButtonClass();
                    this._isProcessingDownKey = false;
                    this._isProcessingUpKey = false;
                    this._isProcessingTabKey = false;
                    this._isProcessingEnterKey = false;
                },

                _isIMEOccludingFlyout: function SearchBox_isIMEOccludingFlyout(imeRect) {
                    var flyoutTop = this._getFlyoutTop();
                    var flyoutBottom = this._getFlyoutBottom();
                    if (((imeRect.top >= flyoutTop) && (imeRect.top <= flyoutBottom)) ||
                        ((imeRect.bottom >= flyoutTop) && (imeRect.bottom <= flyoutBottom))) {
                        return true;
                    }
                    return false;
                },

                _addFlyoutIMEPaddingIfRequired: function SearchBox_addFlyoutIMEPaddingIfRequired() {
                    if (this._isFlyoutShown() && this._isFlyoutBelow() && this._inputElement.msGetInputContext && this._inputElement.msGetInputContext()) {
                        var context = this._inputElement.msGetInputContext();
                        var rect = context.getCandidateWindowClientRect();
                        if (this._isIMEOccludingFlyout(rect)) {
                            var animation = Animations.createRepositionAnimation(this._flyoutDivElement.children);
                            this._flyoutDivElement.style.paddingTop = (rect.bottom - rect.top) + "px";
                            animation.execute();
                        }
                    }
                },

                _msCandidateWindowShowHandler: function SearchBox_msCandidateWindowShowHandler() {
                    this._addFlyoutIMEPaddingIfRequired();
                    this._reflowImeOnPointerRelease = false;
                },

                _msCandidateWindowHideHandler: function SearchBox_msCandidateWindowHideHandler() {
                    if (!this._isFlyoutPointerDown) {
                        var animation = Animations.createRepositionAnimation(this._flyoutDivElement.children);
                        this._flyoutDivElement.style.paddingTop = "";
                        animation.execute();
                    } else {
                        this._reflowImeOnPointerRelease = true;
                    }
                },

                _wireupUserEvents: function SearchBox_wireupUserEvents() {
                    var inputOrImeChangeHandler = this._inputOrImeChangeHandler.bind(this);
                    this._buttonElement.addEventListener("click", this._buttonClickHandler.bind(this));
                    this._inputElement.addEventListener("input", inputOrImeChangeHandler);
                    this._inputElement.addEventListener("keydown", this._keyDownHandler.bind(this));
                    this._inputElement.addEventListener("keypress", this._keyPressHandler.bind(this));
                    this._inputElement.addEventListener("keyup", this._keyUpHandler.bind(this));
                    this._inputElement.addEventListener("focus", this._inputFocusHandler.bind(this));
                    this._inputElement.addEventListener("blur", this._inputBlurHandler.bind(this));
                    _ElementUtilities._addEventListener(this._inputElement, "pointerdown", this._inputPointerDownHandler.bind(this));

                    this._flyoutDivElement.addEventListener("blur", this._flyoutBlurHandler.bind(this));
                    _ElementUtilities._addEventListener(this._flyoutDivElement, "pointerdown", this._flyoutPointerDownHandler.bind(this));
                    _ElementUtilities._addEventListener(this._flyoutDivElement, "pointerup", this._flyoutPointerReleasedHandler.bind(this));
                    _ElementUtilities._addEventListener(this._flyoutDivElement, "pointercancel", this._flyoutPointerReleasedHandler.bind(this));
                    _ElementUtilities._addEventListener(this._flyoutDivElement, "pointerout", this._flyoutPointerReleasedHandler.bind(this));

                    this._inputElement.addEventListener("compositionstart", inputOrImeChangeHandler);
                    this._inputElement.addEventListener("compositionupdate", inputOrImeChangeHandler);
                    this._inputElement.addEventListener("compositionend", inputOrImeChangeHandler);

                    if (this._inputElement.msGetInputContext && this._inputElement.msGetInputContext()) {
                        var context = this._inputElement.msGetInputContext();
                        context.addEventListener("MSCandidateWindowShow", this._msCandidateWindowShowHandler.bind(this));
                        context.addEventListener("MSCandidateWindowHide", this._msCandidateWindowHideHandler.bind(this));
                    }
                },

                _repeaterChangedHandler: function SearchBox_repeaterChangedHandler() {
                    this._updateFlyoutTopAndTouchAction();
                    if (this._isFlyoutShown()) {
                        this._repeaterDivElement.style.display = "none";
                        this._repeaterDivElement.style.display = "block";
                    }
                },

                _wireupRepeaterEvents: function SearchBox_wireupRepeaterEvents() {
                    var repeaterChangeHandler = this._repeaterChangedHandler.bind(this);
                    this._repeater.addEventListener("itemchanged", repeaterChangeHandler);
                    this._repeater.addEventListener("iteminserted", repeaterChangeHandler);
                    this._repeater.addEventListener("itemremoved", repeaterChangeHandler);
                    this._repeater.addEventListener("itemsreloaded", repeaterChangeHandler);
                },

                _inputPointerDownHandler: function SearchBox_inputPointerDownHandler() {
                    if ((_Global.document.activeElement === this._inputElement) && (this._currentSelectedIndex !== -1)) {
                        this._currentFocusedIndex = -1;
                        this._selectSuggestionAtIndex(this._currentFocusedIndex);
                    }
                },

                _flyoutPointerDownHandler: function SearchBox_flyoutPointerDownHandler(ev) {
                    this._isFlyoutPointerDown = true;
                    var srcElement = ev.target;
                    while (srcElement && (srcElement.parentNode !== this._repeaterDivElement)) {
                        srcElement = srcElement.parentNode;
                    }
                    var index = this._findSuggestionElementIndex(srcElement);
                    if ((index >= 0) && (index < this._suggestionsData.length) && (this._currentFocusedIndex !== index)) {
                        if (this._isSuggestionSelectable(this._suggestionsData.getAt(index))) {
                            this._currentFocusedIndex = index;
                            this._selectSuggestionAtIndex(index);
                            this._updateQueryTextWithSuggestionText(this._currentFocusedIndex);
                        }
                    }
                    // Prevent default so focus does not leave input element.
                    ev.preventDefault();
                },

                _flyoutPointerReleasedHandler: function SearchBox_flyoutPointerReleasedHandler() {
                    this._isFlyoutPointerDown = false;

                    if (this._reflowImeOnPointerRelease) {
                        this._reflowImeOnPointerRelease = false;
                        var animation = Animations.createRepositionAnimation(this._flyoutDivElement.children);
                        this._flyoutDivElement.style.paddingTop = "";
                        animation.execute();
                    }
                },

                _isElementInSearchControl: function SearchBox_isElementInSearchControl(targetElement) {
                    return this.element.contains(targetElement) || (this.element === targetElement);
                },

                _hideFlyoutIfLeavingSearchControl: function SearchBox_hideFlyoutIfLeavingSearchControl(targetElement) {
                    if (!this._isFlyoutShown()) {
                        return;
                    }
                    if (!this._isElementInSearchControl(targetElement)) {
                        this._hideFlyout();
                    }
                },

                _wireupWinRTEvents: function SearchBox_wireupWinRTEvents() {
                    this._searchSuggestions.addEventListener("vectorchanged", this._suggestionsChangedHandlerBind);
                    this._searchSuggestionManager.addEventListener("suggestionsrequested", this._suggestionsRequestedHandlerBind);
                },

                _suggestionsChangedHandler: function SearchBox_suggestionsChangedHandler(event) {
                    var collectionChange = event.collectionChange || event.detail.collectionChange;
                    var changeIndex = (+event.index === event.index) ? event.index : event.detail.index;
                    var ChangeEnum = _SearchSuggestionManagerShim._CollectionChange;
                    if (collectionChange === ChangeEnum.reset) {
                        if (this._isFlyoutShown()) {
                            this._hideFlyout();
                        }
                        this._suggestionsData.splice(0, this._suggestionsData.length);
                    } else if (collectionChange === ChangeEnum.itemInserted) {
                        var suggestion = this._searchSuggestions[changeIndex];
                        this._suggestionsData.splice(changeIndex, 0, suggestion);

                        this._showFlyout();

                    } else if (collectionChange === ChangeEnum.itemRemoved) {
                        if ((this._suggestionsData.length === 1)) {
                            _ElementUtilities._setActive(this._inputElement);

                            this._hideFlyout();
                        }
                        this._suggestionsData.splice(changeIndex, 1);
                    } else if (collectionChange === ChangeEnum.itemChanged) {
                        var suggestion = this._searchSuggestions[changeIndex];
                        if (suggestion !== this._suggestionsData.getAt(changeIndex)) {
                            this._suggestionsData.setAt(changeIndex, suggestion);
                        } else {
                            // If the suggestions manager gives us an identical item, it means that only the hit highlighted text has changed.
                            var existingElement = this._repeater.elementFromIndex(changeIndex);
                            if (_ElementUtilities.hasClass(existingElement, ClassName.searchBoxSuggestionQuery)) {
                                this._addHitHighlightedText(existingElement, suggestion, suggestion.text);
                            } else {
                                var resultSuggestionDiv = existingElement.querySelector("." + ClassName.searchBoxSuggestionResultText);
                                if (resultSuggestionDiv) {
                                    this._addHitHighlightedText(resultSuggestionDiv, suggestion, suggestion.text);
                                    var resultSuggestionDetailDiv = existingElement.querySelector("." + ClassName.searchBoxSuggestionResultDetailedText);
                                    if (resultSuggestionDetailDiv) {
                                        this._addHitHighlightedText(resultSuggestionDetailDiv, suggestion, suggestion.detailText);
                                    }
                                }
                            }
                        }
                    }

                    if (_Global.document.activeElement === this._inputElement) {
                        this._updateFakeFocus();
                    }
                },

                _suggestionsRequestedHandler: function SearchBox_suggestionsRequestedHandler(event) {
                    // get the most up to date value of the input langauge from WinRT if available
                    if (_WinRT.Windows.Globalization.Language) {
                        this._lastKeyPressLanguage = _WinRT.Windows.Globalization.Language.currentInputMethodLanguageTag;
                    }

                    var request = event.request || event.detail.request;
                    var deferral;
                    this._fireEvent(SearchBox._EventName.suggestionsrequested, {
                        setPromise: function (promise) {
                            deferral = request.getDeferral();
                            promise.then(function () {
                                deferral.complete();
                            });
                        },
                        searchSuggestionCollection: request.searchSuggestionCollection,
                        language: this._lastKeyPressLanguage,
                        linguisticDetails: this._getLinguisticDetails(true /*useCache*/, true /*createFilled*/),
                        queryText: this._inputElement.value
                    });
                },

                _fireEvent: function SearchBox_fireEvent(type, detail) {
                    // Returns true if ev.preventDefault() was not called
                    var event = _Global.document.createEvent("CustomEvent");
                    event.initCustomEvent(type, true, true, detail);
                    return this.element.dispatchEvent(event);
                },

                _requestingFocusOnKeyboardInputHandler: function SearchBox_requestingFocusOnKeyboardInputHandler() {
                    this._fireEvent(SearchBox._EventName.receivingfocusonkeyboardinput, null);
                    if (_Global.document.activeElement !== this._inputElement) {
                        try {
                            this._inputElement.focus();
                        } catch (e) {
                        }
                    }
                },

                _keydownCaptureHandler: function SearchBox_keydownCaptureHandler(event) {
                    if (this._focusOnKeyboardInput && this._shouldKeyTriggerTypeToSearch(event)) {
                        this._requestingFocusOnKeyboardInputHandler(event);
                    }
                },

                _frameLoadCaptureHandler: function SearchBox_frameLoadCaptureHandler(event) {
                    if (this._focusOnKeyboardInput) {
                        this._updateKeydownCaptureListeners(event.target.contentWindow, true /*add*/);
                    }
                },

                _updateKeydownCaptureListeners: function SearchBox_updateTypeToSearchListeners(win, add) {
                    // Register for child frame keydown events in order to support FocusOnKeyboardInput
                    // when focus is in a child frame.  Also register for child frame load events so
                    // it still works after frame navigations.
                    // Note: This won't catch iframes added programmatically later, but that can be worked
                    // around by toggling FocusOnKeyboardInput off/on after the new iframe is added.
                    try {
                        if (add) {
                            win.document.addEventListener('keydown', this._keydownCaptureHandlerBind, true);
                        } else {
                            win.document.removeEventListener('keydown', this._keydownCaptureHandlerBind, true);
                        }
                    } catch (e) { // if the IFrame crosses domains, we'll get a permission denied error
                    }

                    if (win.frames) {
                        for (var i = 0, l = win.frames.length; i < l; i++) {
                            var childWin = win.frames[i];
                            this._updateKeydownCaptureListeners(childWin, add);

                            try {
                                if (add) {
                                    if (childWin.frameElement) {
                                        childWin.frameElement.addEventListener('load', this._frameLoadCaptureHandlerBind, true);
                                    }
                                } else {
                                    if (childWin.frameElement) {
                                        childWin.frameElement.removeEventListener('load', this._frameLoadCaptureHandlerBind, true);
                                    }
                                }
                            } catch (e) { // if the IFrame crosses domains, we'll get a permission denied error
                            }
                        }
                    }
                },

                _shouldKeyTriggerTypeToSearch: function SearchBox_shouldKeyTriggerTypeToSearch(event) {
                    var shouldTrigger = false;
                    // First, check if a metaKey is pressed (only applies to MacOS). If so, do nothing here.
                    if (!event.metaKey) {
                        // We also don't handle CTRL/ALT combinations, unless ALTGR is also set. Since there is no shortcut for checking AltGR,
                        // we need to use getModifierState, however, Safari currently doesn't support this.
                        if ((!event.ctrlKey && !event.altKey) || (event.getModifierState && event.getModifierState("AltGraph"))) {
                            // Show on most keys for visible characters like letters, numbers, etc.
                            switch (event.keyCode) {
                                case 0x30:  //0x30 0 key
                                case 0x31:  //0x31 1 key
                                case 0x32:  //0x32 2 key
                                case 0x33:  //0x33 3 key
                                case 0x34:  //0x34 4 key
                                case 0x35:  //0x35 5 key
                                case 0x36:  //0x36 6 key
                                case 0x37:  //0x37 7 key
                                case 0x38:  //0x38 8 key
                                case 0x39:  //0x39 9 key

                                case 0x41:  //0x41 A key
                                case 0x42:  //0x42 B key
                                case 0x43:  //0x43 C key
                                case 0x44:  //0x44 D key
                                case 0x45:  //0x45 E key
                                case 0x46:  //0x46 F key
                                case 0x47:  //0x47 G key
                                case 0x48:  //0x48 H key
                                case 0x49:  //0x49 I key
                                case 0x4A:  //0x4A J key
                                case 0x4B:  //0x4B K key
                                case 0x4C:  //0x4C L key
                                case 0x4D:  //0x4D M key
                                case 0x4E:  //0x4E N key
                                case 0x4F:  //0x4F O key
                                case 0x50:  //0x50 P key
                                case 0x51:  //0x51 Q key
                                case 0x52:  //0x52 R key
                                case 0x53:  //0x53 S key
                                case 0x54:  //0x54 T key
                                case 0x55:  //0x55 U key
                                case 0x56:  //0x56 V key
                                case 0x57:  //0x57 W key
                                case 0x58:  //0x58 X key
                                case 0x59:  //0x59 Y key
                                case 0x5A:  //0x5A Z key

                                case 0x60:  // VK_NUMPAD0,             //0x60 Numeric keypad 0 key
                                case 0x61:  // VK_NUMPAD1,             //0x61 Numeric keypad 1 key
                                case 0x62:  // VK_NUMPAD2,             //0x62 Numeric keypad 2 key
                                case 0x63:  // VK_NUMPAD3,             //0x63 Numeric keypad 3 key
                                case 0x64:  // VK_NUMPAD4,             //0x64 Numeric keypad 4 key
                                case 0x65:  // VK_NUMPAD5,             //0x65 Numeric keypad 5 key
                                case 0x66:  // VK_NUMPAD6,             //0x66 Numeric keypad 6 key
                                case 0x67:  // VK_NUMPAD7,             //0x67 Numeric keypad 7 key
                                case 0x68:  // VK_NUMPAD8,             //0x68 Numeric keypad 8 key
                                case 0x69:  // VK_NUMPAD9,             //0x69 Numeric keypad 9 key
                                case 0x6A:  // VK_MULTIPLY,            //0x6A Multiply key
                                case 0x6B:  // VK_ADD,                 //0x6B Add key
                                case 0x6C:  // VK_SEPARATOR,           //0x6C Separator key
                                case 0x6D:  // VK_SUBTRACT,            //0x6D Subtract key
                                case 0x6E:  // VK_DECIMAL,             //0x6E Decimal key
                                case 0x6F:  // VK_DIVIDE,              //0x6F Divide key

                                case 0xBA:  // VK_OEM_1,               //0xBA Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ';:' key
                                case 0xBB:  // VK_OEM_PLUS,            //0xBB For any country/region, the '+' key
                                case 0xBC:  // VK_OEM_COMMA,           //0xBC For any country/region, the ',' key
                                case 0xBD:  // VK_OEM_MINUS,           //0xBD For any country/region, the '-' key
                                case 0xBE:  // VK_OEM_PERIOD,          //0xBE For any country/region, the '.' key
                                case 0xBF:  // VK_OEM_2,               //0xBF Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '/?' key
                                case 0xC0:  // VK_OEM_3,               //0xC0 Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '`~' key

                                case 0xDB:  // VK_OEM_4,               //0xDB Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '[{' key
                                case 0xDC:  // VK_OEM_5,               //0xDC Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '\|' key
                                case 0xDD:  // VK_OEM_6,               //0xDD Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ']}' key
                                case 0xDE:  // VK_OEM_7,               //0xDE Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the 'single-quote/double-quote' key
                                case 0xDF:  // VK_OEM_8,               //0xDF Used for miscellaneous characters; it can vary by keyboard.

                                case 0xE2:  // VK_OEM_102,             //0xE2 Either the angle bracket key or the backslash key on the RT 102-key keyboard

                                case 0xE5:  // VK_PROCESSKEY,          //0xE5 IME PROCESS key

                                case 0xE7:  // VK_PACKET,              //0xE7 Used to pass Unicode characters as if they were keystrokes. The VK_PACKET key is the low word of a 32-bit Virtual Key value used for non-keyboard input methods. For more information, see Remark in KEYBDINPUT, SendInput, WM_KEYDOWN, and WM_KEYUP
                                    shouldTrigger = true;
                                    break;
                            }
                        }
                    }
                    return shouldTrigger;
                },

                _hasLinguisticDetailsChanged: function SearchBox_hasLinguisticDetailsChanged(newLinguisticDetails) {
                    var hasLinguisticDetailsChanged = false;
                    if ((this._prevLinguisticDetails.queryTextCompositionStart !== newLinguisticDetails.queryTextCompositionStart) ||
                        (this._prevLinguisticDetails.queryTextCompositionLength !== newLinguisticDetails.queryTextCompositionLength) ||
                        (this._prevLinguisticDetails.queryTextAlternatives.length !== newLinguisticDetails.queryTextAlternatives.length)) {
                        hasLinguisticDetailsChanged = true;
                    }
                    this._prevLinguisticDetails = newLinguisticDetails;
                    return hasLinguisticDetailsChanged;
                },

                _isProcessingImeFocusLossKey: function SearchBox_isProcessingImeFocusLossKey() {
                    return this._isProcessingDownKey || this._isProcessingUpKey || this._isProcessingTabKey || this._isProcessingEnterKey;
                },

                _updateQueryTextWithSuggestionText: function SearchBox_updateQueryTextWithSuggestionText(suggestionIndex) {
                    if ((suggestionIndex >= 0) && (suggestionIndex < this._suggestionsData.length)) {
                        this.queryText = this._suggestionsData.getAt(suggestionIndex).text;
                    }
                }

            }, {
                _EventName: {
                    querychanged: EventName.querychanged,
                    querysubmitted: EventName.querysubmitted,
                    resultsuggestionchosen: EventName.resultsuggestionchosen,
                    suggestionsrequested: EventName.suggestionsrequested,
                    receivingfocusonkeyboardinput: EventName.receivingfocusonkeyboardinput
                },

                _Constants: {
                    MIN_POPUP_HEIGHT: 152,
                },

                createResultSuggestionImage: function SearchBox_createResultSuggestionImage(url) {
                    /// <signature helpKeyword="WinJS.UI.SearchBox.createResultSuggestionImage">
                    /// <summary locid="WinJS.UI.SearchBox.createResultSuggestionImage">
                    /// Creates the image argument for SearchSuggestionCollection.appendResultSuggestion.
                    /// </summary>
                    /// <param name="url" type="string" locid="WinJS.UI.SearchBox.SearchBox_createResultSuggestionImage_p:url">
                    /// The url of the image.
                    /// </param>
                    /// <compatibleWith platform="Windows" minVersion="8.1"/>
                    /// </signature>
                    if (_WinRT.Windows.Foundation.Uri && _WinRT.Windows.Storage.Streams.RandomAccessStreamReference) {
                        return _WinRT.Windows.Storage.Streams.RandomAccessStreamReference.createFromUri(new _WinRT.Windows.Foundation.Uri(url));
                    }
                    return url;
                },

                _getKeyModifiers: function SearchBox_getKeyModifiers(ev) {

                    // Returns the same value as http://msdn.microsoft.com/en-us/library/windows/apps/xaml/windows.system.virtualkeymodifiers
                    var VirtualKeys = {
                        ctrlKey: 1,
                        altKey: 2,
                        shiftKey: 4
                    };

                    var keyModifiers = 0;
                    if (ev.ctrlKey) {
                        keyModifiers |= VirtualKeys.ctrlKey;
                    }
                    if (ev.altKey) {
                        keyModifiers |= VirtualKeys.altKey;
                    }
                    if (ev.shiftKey) {
                        keyModifiers |= VirtualKeys.shiftKey;
                    }
                    return keyModifiers;
                },

                _sortAndMergeHits: function searchBox_sortAndMergeHits(hitsProvided) {
                    var reducedHits = [];
                    if (hitsProvided) {
                        // Copy hitsprovided array as winrt objects are immutable.
                        var hits = new Array(hitsProvided.length);
                        for (var i = 0; i < hitsProvided.length; i++) {
                            hits.push({ startPosition: hitsProvided[i].startPosition, length: hitsProvided[i].length });
                        }
                        hits.sort(SearchBox._hitStartPositionAscendingSorter);
                        hits.reduce(SearchBox._hitIntersectionReducer, reducedHits);
                    }
                    return reducedHits;
                },

                _hitStartPositionAscendingSorter: function searchBox_hitStartPositionAscendingSorter(firstHit, secondHit) {
                    var returnValue = 0;
                    if (firstHit.startPosition < secondHit.startPosition) {
                        returnValue = -1;
                    } else if (firstHit.startPosition > secondHit.startPosition) {
                        returnValue = 1;
                    }
                    return returnValue;
                },

                _hitIntersectionReducer: function searchBox_hitIntersectionReducer(reducedHits, nextHit, currentIndex) {
                    if (currentIndex === 0) {
                        reducedHits.push(nextHit);
                    } else {
                        var curHit = reducedHits[reducedHits.length - 1];
                        var curHitEndPosition = curHit.startPosition + curHit.length;
                        if (nextHit.startPosition <= curHitEndPosition) {
                            // The next hit intersects or is next to current hit. Merge it.
                            var nextHitEndPosition = nextHit.startPosition + nextHit.length;
                            if (nextHitEndPosition > curHitEndPosition) {
                                curHit.length = nextHitEndPosition - curHit.startPosition;
                            }
                        } else {
                            // No intersection, simply add to reduced list.
                            reducedHits.push(nextHit);
                        }
                    }
                    return reducedHits;
                },

                _isTypeToSearchKey: function searchBox_isTypeToSearchKey(event) {
                    if (event.shiftKey || event.ctrlKey || event.altKey) {
                        return false;
                    }
                    return true;
                }
            });
            _Base.Class.mix(SearchBox, _Control.DOMEventMixin);
            return SearchBox;
        })
    });

});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
/// <dictionary>appbar,Flyout,Flyouts,registeredforsettings,SettingsFlyout,Statics,Syriac</dictionary>
define('WinJS/Controls/SettingsFlyout',[
    '../Core/_Global',
    '../Core/_WinRT',
    '../Core/_Base',
    '../Core/_BaseUtils',
    '../Core/_ErrorFromName',
    '../Core/_Resources',
    '../Core/_WriteProfilerMark',
    '../Animations',
    '../Pages',
    '../Promise',
    '../Utilities/_Dispose',
    '../Utilities/_ElementUtilities',
    '../Utilities/_ElementListUtilities',
    '../Utilities/_Hoverable',
    './AppBar/_Constants',
    './Flyout/_Overlay',
    'require-style!less/desktop/controls',
    'require-style!less/phone/controls'
    ], function settingsFlyoutInit(_Global,_WinRT, _Base, _BaseUtils, _ErrorFromName, _Resources, _WriteProfilerMark, Animations, Pages, Promise, _Dispose, _ElementUtilities, _ElementListUtilities, _Hoverable, _Constants, _Overlay) {
    "use strict";

    _Base.Namespace.define("WinJS.UI", {
        /// <field>
        /// <summary locid="WinJS.UI.SettingsFlyout">Provides users with fast, in-context access to settings that affect the current app.</summary>
        /// <compatibleWith platform="Windows" minVersion="8.0"/>
        /// </field>
        /// <name locid="WinJS.UI.SettingsFlyout_name">Settings Flyout</name>
        /// <icon src="ui_winjs.ui.settingsflyout.12x12.png" width="12" height="12" />
        /// <icon src="ui_winjs.ui.settingsflyout.16x16.png" width="16" height="16" />
        /// <htmlSnippet supportsContent="true"><![CDATA[<div data-win-control="WinJS.UI.SettingsFlyout">
        /// <div class="win-header">
        /// <button type="button" onclick="WinJS.UI.SettingsFlyout.show()" class="win-backbutton"></button>
        /// <div class="win-label">Custom Settings</div>
        /// </div>
        /// <div class="win-content">
        /// {Your Content Here}
        /// </div>
        /// </div>]]></htmlSnippet>
        /// <event name="beforeshow" locid="WinJS.UI.SettingsFlyout_e:beforeshow">Raised just before showing a SettingsFlyout.</event>
        /// <event name="aftershow" locid="WinJS.UI.SettingsFlyout_e:aftershow">Raised immediately after a SettingsFlyout is fully shown.</event>
        /// <event name="beforehide" locid="WinJS.UI.SettingsFlyout_e:beforehide">Raised just before hiding a SettingsFlyout.</event>
        /// <event name="afterhide" locid="WinJS.UI.SettingsFlyout_e:afterhide">Raised immediately after a SettingsFlyout is fully hidden.</event>
        /// <part name="settings" class="win-settingsflyout" locid="WinJS.UI.SettingsFlyout_part:settings">The SettingsFlyout control itself.</part>
        /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
        /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
        /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
        SettingsFlyout: _Base.Namespace._lazy(function () {
            var Key = _ElementUtilities.Key;

            var settingsPageIsFocusedOnce;

            // Constants for width
            var settingsNarrow = "narrow",
                settingsWide = "wide";

            // Determine if the settings pane (system language) is RTL or not.
            function _shouldAnimateFromLeft() {
                if (_WinRT.Windows.UI.ApplicationSettings.SettingsEdgeLocation) {
                    var appSettings = _WinRT.Windows.UI.ApplicationSettings;
                    return (appSettings.SettingsPane.edge === appSettings.SettingsEdgeLocation.left);
                } else {
                    return false;
                }
            }

            // Get the settings control by matching the settingsCommandId
            // if no match we'll try to match element id
            function _getChildSettingsControl(parentElement, id) {
                var settingElements = parentElement.querySelectorAll("." + _Constants.settingsFlyoutClass);
                var retValue,
                    control;
                for (var i = 0; i < settingElements.length; i++) {
                    control = settingElements[i].winControl;
                    if (control) {
                        if (control.settingsCommandId === id) {
                            retValue = control;
                            break;
                        }
                        if (settingElements[i].id === id) {
                            retValue = retValue || control;
                        }
                    }
                }

                return retValue;
            }

            var SettingsFlyout = _Base.Class.derive(_Overlay._Overlay, function SettingsFlyout_ctor(element, options) {
                /// <signature helpKeyword="WinJS.UI.SettingsFlyout.SettingsFlyout">
                /// <summary locid="WinJS.UI.SettingsFlyout.constructor">Creates a new SettingsFlyout control.</summary>
                /// <param name="element" type="HTMLElement" domElement="true" locid="WinJS.UI.SettingsFlyout.constructor_p:element">
                /// The DOM element that will host the control.
                /// </param>
                /// <param name="options" type="Object" locid="WinJS.UI.SettingsFlyout.constructor_p:options">
                /// The set of properties and values to apply to the new SettingsFlyout.
                /// </param>
                /// <returns type="WinJS.UI.SettingsFlyout" locid="WinJS.UI.SettingsFlyout.constructor_returnValue">The new SettingsFlyout control.</returns>
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </signature>

                // Make sure there's an input element
                this._element = element || _Global.document.createElement("div");
                this._id = this._element.id || _ElementUtilities._uniqueID(this._element);
                this._writeProfilerMark("constructor,StartTM");

                // Call the base overlay constructor helper
                this._baseOverlayConstructor(this._element, options);

                this._addFirstDiv();
                this._addFinalDiv();

                // Handle "esc" & "tab" key presses
                this._element.addEventListener("keydown", this._handleKeyDown, true);

                // Make a click eating div
                _Overlay._Overlay._createClickEatingDivAppBar();

                // Start settings hidden
                this._element.style.visibilty = "hidden";
                this._element.style.display = "none";

                // Attach our css class
                _ElementUtilities.addClass(this._element, _Constants.settingsFlyoutClass);

                // apply the light theme styling to the win-content elements inside the SettingsFlyout
                _ElementListUtilities.query("div.win-content", this._element).
                    forEach(function (e) {
                        if (!_ElementUtilities._matchesSelector(e, '.win-ui-dark, .win-ui-dark *')){
                            _ElementUtilities.addClass(e, _Constants.flyoutLightClass);
                        }
                    });

                // Make sure we have an ARIA role
                var role = this._element.getAttribute("role");
                if (role === null || role === "" || role === undefined) {
                    this._element.setAttribute("role", "dialog");
                }
                var label = this._element.getAttribute("aria-label");
                if (label === null || label === "" || label === undefined) {
                    this._element.setAttribute("aria-label", strings.ariaLabel);
                }

                // Make sure _Overlay event handlers are hooked up
                this._addOverlayEventHandlers(true);

                // Make sure animations are hooked up
                this._currentAnimateIn = this._animateSlideIn;
                this._currentAnimateOut = this._animateSlideOut;
                this._writeProfilerMark("constructor,StopTM");
            }, {
                // Public Properties

                /// <field type="String" defaultValue="narrow" oamOptionsDatatype="WinJS.UI.SettingsFlyout.width" locid="WinJS.UI.SettingsFlyout.width" helpKeyword="WinJS.UI.SettingsFlyout.width">
                /// Width of the SettingsFlyout, "narrow", or "wide".
                /// <deprecated type="deprecate">
                /// SettingsFlyout.width may be altered or unavailable in future versions. Instead, style the CSS width property on elements with the .win-settingsflyout class.
                /// </deprecated>
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                width: {
                    get: function () {
                        return this._width;
                    },

                    set: function (value) {
                        _ElementUtilities._deprecated(strings.widthDeprecationMessage);
                        if (value === this._width) {
                            return;
                        }
                        // Get rid of old class
                        if (this._width === settingsNarrow) {
                            _ElementUtilities.removeClass(this._element, _Constants.narrowClass);
                        } else if (this._width === settingsWide) {
                            _ElementUtilities.removeClass(this._element, _Constants.wideClass);
                        }
                        this._width = value;

                        // Attach our new css class
                        if (this._width === settingsNarrow) {
                            _ElementUtilities.addClass(this._element, _Constants.narrowClass);
                        } else if (this._width === settingsWide) {
                            _ElementUtilities.addClass(this._element, _Constants.wideClass);
                        }
                    }
                },

                /// <field type="String" locid="WinJS.UI.SettingsFlyout.settingsCommandId" helpKeyword="WinJS.UI.SettingsFlyout.settingsCommandId">
                /// Define the settings command Id for the SettingsFlyout control.
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </field>
                settingsCommandId: {
                    get: function () {
                        return this._settingsCommandId;
                    },

                    set: function (value) {
                        this._settingsCommandId = value;
                    }
                },

                show: function () {
                    /// <signature helpKeyword="WinJS.UI.SettingsFlyout.show">
                    /// <summary locid="WinJS.UI.SettingsFlyout.show">
                    /// Shows the SettingsFlyout, if hidden.
                    /// </summary>
                    /// <compatibleWith platform="Windows" minVersion="8.0"/>
                    /// </signature>
                    // Just call private version to make appbar flags happy

                    // Don't do anything if disabled
                    if (this.disabled) {
                        return;
                    }
                    this._writeProfilerMark("show,StartTM"); // The corresponding "stop" profiler mark is handled in _Overlay._baseEndShow().
                    this._show();
                },

                _dispose: function SettingsFlyout_dispose() {
                    _Dispose.disposeSubTree(this.element);
                    this._dismiss();
                },

                _show: function SettingsFlyout_show() {
                    // We call our base "_baseShow" because SettingsFlyout overrides show
                    this._baseShow();
                    // Need click-eating div to be visible,
                    // (even if now hiding, we'll show and need click eater)
                    _Overlay._Overlay._showClickEatingDivAppBar();
                },

                _endShow: function SettingsFlyout_endShow() {
                    // Clean up after showing
                    this._initAfterAnimation();
                },

                _initAfterAnimation: function SettingsFlyout_initAfterAnimation() {
                    settingsPageIsFocusedOnce = 0;

                    // Verify that the firstDiv and finalDiv are in the correct location.
                    // Move them to the correct location or add them if they are not.
                    if (!_ElementUtilities.hasClass(this.element.children[0], _Constants.firstDivClass)) {
                        var firstDiv = this.element.querySelectorAll(".win-first");
                        if (firstDiv && firstDiv.length > 0) {
                            firstDiv.item(0).parentNode.removeChild(firstDiv.item(0));
                        }

                        this._addFirstDiv();
                    }

                    // Set focus to the firstDiv
                    if (this.element.children[0]) {
                        _ElementUtilities._addEventListener(this.element.children[0], "focusout", function () { settingsPageIsFocusedOnce = 1; }, false);
                        this.element.children[0].focus();
                    }

                    if (!_ElementUtilities.hasClass(this.element.children[this.element.children.length - 1], _Constants.finalDivClass)) {
                        var finalDiv = this.element.querySelectorAll(".win-final");
                        if (finalDiv && finalDiv.length > 0) {
                            finalDiv.item(0).parentNode.removeChild(finalDiv.item(0));
                        }

                        this._addFinalDiv();
                    }

                    this._setBackButtonsAriaLabel();
                },

                _setBackButtonsAriaLabel: function SettingsFlyout_setBackButtonsAriaLabel() {
                    var backbuttons = this.element.querySelectorAll(".win-backbutton");
                    var label;
                    for (var i = 0; i < backbuttons.length; i++) {
                        label = backbuttons[i].getAttribute("aria-label");
                        if (label === null || label === "" || label === undefined) {
                            backbuttons[i].setAttribute("aria-label", strings.backbuttonAriaLabel);
                        }
                    }
                },

                hide: function () {
                    /// <signature helpKeyword="WinJS.UI.SettingsFlyout.hide">
                    /// <summary locid="WinJS.UI.SettingsFlyout.hide">
                    /// Hides the SettingsFlyout, if visible, regardless of other state.
                    /// </summary>
                    /// <compatibleWith platform="Windows" minVersion="8.0"/>
                    /// </signature>
                    // Just call private version to make appbar flags happy
                    this._writeProfilerMark("hide,StartTM"); // The corresponding "stop" profiler mark is handled in _Overlay._baseEndHide().
                    this._hide();
                },

                _hide: function SettingsFlyout_hide() {
                    if (this._baseHide()) {
                        // Need click-eating div to be hidden
                        _Overlay._Overlay._hideClickEatingDivAppBar();
                    }
                },

                // SettingsFlyout animations
                _animateSlideIn: function SettingsFlyout_animateSlideIn() {
                    var animateFromLeft = _shouldAnimateFromLeft();
                    var offset = animateFromLeft ? "-100px" : "100px";
                    _ElementListUtilities.query("div.win-content", this._element).
                        forEach(function (e) { Animations.enterPage(e, { left: offset }); });

                    var where,
                        width = this._element.offsetWidth;
                    // Slide in from right side or left side?
                    if (animateFromLeft) {
                        // RTL
                        where = { top: "0px", left: "-" + width + "px" };
                        this._element.style.right = "auto";
                        this._element.style.left = "0px";
                    } else {
                        // From right side
                        where = { top: "0px", left: width + "px" };
                        this._element.style.right = "0px";
                        this._element.style.left = "auto";
                    }

                    this._element.style.opacity = 1;
                    this._element.style.visibility = "visible";

                    return Animations.showPanel(this._element, where);
                },

                _animateSlideOut: function SettingsFlyout_animateSlideOut() {
                    var where,
                        width = this._element.offsetWidth;
                    if (_shouldAnimateFromLeft()) {
                        // RTL
                        where = { top: "0px", left: width + "px" };
                        this._element.style.right = "auto";
                        this._element.style.left = "-" + width + "px";
                    } else {
                        // From right side
                        where = { top: "0px", left: "-" + width + "px" };
                        this._element.style.right = "-" + width + "px";
                        this._element.style.left = "auto";
                    }

                    return Animations.showPanel(this._element, where);
                },

                _fragmentDiv: {
                    get: function SettingsFlyout_fragmentDiv_get() {
                        return this._fragDiv;
                    },

                    set: function SettingsFlyout_fragmentDiv_set(value) {
                        this._fragDiv = value;
                    }
                },

                _unloadPage: function SettingsFlyout_unloadPage(event) {
                    var settingsControl = event.currentTarget.winControl;
                    settingsControl.removeEventListener(_Overlay._Overlay.afterHide, this._unloadPage, false);

                    Promise.as().then(function () {
                        if (settingsControl._fragmentDiv) {
                            _Global.document.body.removeChild(settingsControl._fragmentDiv);
                            settingsControl._fragmentDiv = null;
                        }
                    });
                },

                _dismiss: function SettingsFlyout_dismiss() {
                    this.addEventListener(_Overlay._Overlay.afterHide, this._unloadPage, false);
                    this._hide();
                },

                _handleKeyDown: function SettingsFlyout_handleKeyDown(event) {
                    if (event.keyCode === Key.escape) {
                        event.preventDefault();
                        event.stopPropagation();
                        this.winControl._dismiss();
                    } else if ((event.keyCode === Key.space || event.keyCode === Key.enter)
                           && (this.children[0] === _Global.document.activeElement)) {
                        event.preventDefault();
                        event.stopPropagation();
                        this.winControl._dismiss();
                    } else if (event.shiftKey && event.keyCode === Key.tab
                    && this.children[0] === _Global.document.activeElement) {
                        event.preventDefault();
                        event.stopPropagation();
                        var _elms = this.getElementsByTagName("*");

                        for (var i = _elms.length - 2; i >= 0; i--) {
                            _elms[i].focus();

                            if (_elms[i] === _Global.document.activeElement) {
                                break;
                            }
                        }
                    }
                },

                _focusOnLastFocusableElementFromParent: function SettingsFlyout_focusOnLastFocusableElementFromParent() {
                    var active = _Global.document.activeElement;
                    if (!settingsPageIsFocusedOnce || !active || !_ElementUtilities.hasClass(active, _Constants.firstDivClass)) {
                        return;
                    }

                    var _elms = this.parentElement.getElementsByTagName("*");

                    // There should be at least 1 element in addition to the firstDiv & finalDiv
                    if (_elms.length <= 2) {
                        return;
                    }

                    // Get the tabIndex set to the finalDiv (which is the highest)
                    var _highestTabIndex = _elms[_elms.length - 1].tabIndex;

                    // If there are positive tabIndices, set focus to the element with the highest tabIndex.
                    // Otherwise set focus to the last focusable element in DOM order.
                    var i;
                    if (_highestTabIndex) {
                        for (i = _elms.length - 2; i > 0; i--) {
                            if (_elms[i].tabIndex === _highestTabIndex) {
                                _elms[i].focus();
                                break;
                            }
                        }
                    } else {
                        for (i = _elms.length - 2; i > 0; i--) {
                            // Skip <div> with undefined tabIndex (To work around Win8 bug #622245)
                            if ((_elms[i].tagName !== "DIV") || (_elms[i].getAttribute("tabIndex") !== null)) {
                                _elms[i].focus();

                                if (_elms[i] === _Global.document.activeElement) {
                                    break;
                                }
                            }
                        }
                    }
                },

                _focusOnFirstFocusableElementFromParent: function SettingsFlyout_focusOnFirstFocusableElementFromParent() {
                    var active = _Global.document.activeElement;
                    if (!active || !_ElementUtilities.hasClass(active, _Constants.finalDivClass)) {
                        return;
                    }
                    var _elms = this.parentElement.getElementsByTagName("*");

                    // There should be at least 1 element in addition to the firstDiv & finalDiv
                    if (_elms.length <= 2) {
                        return;
                    }

                    // Get the tabIndex set to the firstDiv (which is the lowest)
                    var _lowestTabIndex = _elms[0].tabIndex;

                    // If there are positive tabIndices, set focus to the element with the lowest tabIndex.
                    // Otherwise set focus to the first focusable element in DOM order.
                    var i;
                    if (_lowestTabIndex) {
                        for (i = 1; i < _elms.length - 1; i++) {
                            if (_elms[i].tabIndex === _lowestTabIndex) {
                                _elms[i].focus();
                                break;
                            }
                        }
                    } else {
                        for (i = 1; i < _elms.length - 1; i++) {
                            // Skip <div> with undefined tabIndex (To work around Win8 bug #622245)
                            if ((_elms[i].tagName !== "DIV") || (_elms[i].getAttribute("tabIndex") !== null)) {
                                _elms[i].focus();

                                if (_elms[i] === _Global.document.activeElement) {
                                    break;
                                }
                            }
                        }
                    }
                },

                // Create and add a new first div to the beginning of the list
                _addFirstDiv: function SettingsFlyout_addFirstDiv() {
                    var _elms = this._element.getElementsByTagName("*");
                    var _minTab = 0;
                    for (var i = 0; i < _elms.length; i++) {
                        if ((0 < _elms[i].tabIndex) && (_minTab === 0 || _elms[i].tabIndex < _minTab)) {
                            _minTab = _elms[i].tabIndex;
                        }
                    }
                    var firstDiv = _Global.document.createElement("div");
                    firstDiv.className = _Constants.firstDivClass;
                    firstDiv.style.display = "inline";
                    firstDiv.setAttribute("role", "menuitem");
                    firstDiv.setAttribute("aria-hidden", "true");
                    firstDiv.tabIndex = _minTab;
                    _ElementUtilities._addEventListener(firstDiv, "focusin", this._focusOnLastFocusableElementFromParent, false);

                    // add to beginning
                    if (this._element.children[0]) {
                        this._element.insertBefore(firstDiv, this._element.children[0]);
                    } else {
                        this._element.appendChild(firstDiv);
                    }
                },

                // Create and add a new final div to the end of the list
                _addFinalDiv: function SettingsFlyout_addFinalDiv() {
                    var _elms = this._element.getElementsByTagName("*");
                    var _maxTab = 0;
                    for (var i = 0; i < _elms.length; i++) {
                        if (_elms[i].tabIndex > _maxTab) {
                            _maxTab = _elms[i].tabIndex;
                        }
                    }
                    var finalDiv = _Global.document.createElement("div");
                    finalDiv.className = _Constants.finalDivClass;
                    finalDiv.style.display = "inline";
                    finalDiv.setAttribute("role", "menuitem");
                    finalDiv.setAttribute("aria-hidden", "true");
                    finalDiv.tabIndex = _maxTab;
                    _ElementUtilities._addEventListener(finalDiv, "focusin", this._focusOnFirstFocusableElementFromParent, false);

                    this._element.appendChild(finalDiv);
                },

                _writeProfilerMark: function SettingsFlyout_writeProfilerMark(text) {
                    _WriteProfilerMark("WinJS.UI.SettingsFlyout:" + this._id + ":" + text);
                }
            });

            // Statics
            SettingsFlyout.show = function () {
                /// <signature helpKeyword="WinJS.UI.SettingsFlyout.show">
                /// <summary locid="WinJS.UI.SettingsFlyout.show_static">
                /// Shows the SettingsPane UI, if hidden, regardless of other states.
                /// </summary>
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </signature>
                /// Show the main settings pane
                if (_WinRT.Windows.UI.ApplicationSettings.SettingsPane) {
                    _WinRT.Windows.UI.ApplicationSettings.SettingsPane.show();
                }
                // And hide the WWA one
                var elements = _Global.document.querySelectorAll('div[data-win-control="WinJS.UI.SettingsFlyout"]');
                var len = elements.length;
                for (var i = 0; i < len; i++) {
                    var settingsFlyout = elements[i].winControl;
                    if (settingsFlyout) {
                        settingsFlyout._dismiss();
                    }
                }
            };

            var _settingsEvent = { event: undefined };
            SettingsFlyout.populateSettings = function (e) {
                /// <signature helpKeyword="WinJS.UI.SettingsFlyout.populateSettings">
                /// <summary locid="WinJS.UI.SettingsFlyout.populateSettings">
                /// Loads a portion of the SettingsFlyout. Your app calls this when the user invokes a settings command and the WinJS.Application.onsettings event occurs.
                /// </summary>
                /// <param name="e" type="Object" locid="WinJS.UI.SettingsFlyout.populateSettings_p:e">
                /// An object that contains information about the event, received from the WinJS.Application.onsettings event. The detail property of this object contains
                /// the applicationcommands sub-property that you set to an array of settings commands.
                /// </param>
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </signature>
                _settingsEvent.event = e.detail;

                if (_settingsEvent.event.applicationcommands) {
                    var n = _WinRT.Windows.UI.ApplicationSettings;
                    Object.keys(_settingsEvent.event.applicationcommands).forEach(function (name) {
                        var setting = _settingsEvent.event.applicationcommands[name];
                        if (!setting.title) { setting.title = name; }
                        var command = new n.SettingsCommand(name, setting.title, SettingsFlyout._onSettingsCommand);
                        _settingsEvent.event.e.request.applicationCommands.append(command);
                    });
                }
            };

            SettingsFlyout._onSettingsCommand = function (command) {
                var id = command.id;
                if (_settingsEvent.event.applicationcommands && _settingsEvent.event.applicationcommands[id]) {
                    SettingsFlyout.showSettings(id, _settingsEvent.event.applicationcommands[id].href);
                }
            };

            SettingsFlyout.showSettings = function (id, path) {
                /// <signature helpKeyword="WinJS.UI.SettingsFlyout.showSettings">
                /// <summary locid="WinJS.UI.SettingsFlyout.showSettings">
                /// Show the SettingsFlyout using the settings element identifier (ID) and the path of the page that contains the settings element.
                /// </summary>
                /// <param name="id" type="String" locid="WinJS.UI.SettingsFlyout.showSettings_p:id">
                /// The ID of the settings element.
                /// </param>
                /// <param name="path" type="Object" locid="WinJS.UI.SettingsFlyout.showSettings_p:path">
                ///  The path of the page that contains the settings element.
                /// </param>
                /// <compatibleWith platform="Windows" minVersion="8.0"/>
                /// </signature>
                var control = _getChildSettingsControl(_Global.document, id);
                if (control) {
                    control.show();
                } else if (path) {
                    var divElement = _Global.document.createElement("div");
                    divElement = _Global.document.body.appendChild(divElement);
                    Pages.render(path, divElement).then(function () {
                        control = _getChildSettingsControl(divElement, id);
                        if (control) {
                            control._fragmentDiv = divElement;
                            control.show();
                        } else {
                            _Global.document.body.removeChild(divElement);
                        }
                    });
                } else {
                    throw new _ErrorFromName("WinJS.UI.SettingsFlyout.BadReference", strings.badReference);
                }
            };

            var strings = {
                get ariaLabel() { return _Resources._getWinJSString("ui/settingsFlyoutAriaLabel").value; },
                get badReference() { return "Invalid argument: Invalid href to settings flyout fragment"; },
                get backbuttonAriaLabel() { return _Resources._getWinJSString("ui/backbuttonarialabel").value; },
                get widthDeprecationMessage() { return "SettingsFlyout.width may be altered or unavailable in future versions. Instead, style the CSS width property on elements with the .win-settingsflyout class."; },
            };

            return SettingsFlyout;
        })
    });


});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/NavBar/_Command',[
    'exports',
    '../../Core/_Global',
    '../../Core/_Base',
    '../../Core/_ErrorFromName',
    '../../Core/_Resources',
    '../../ControlProcessor',
    '../../Navigation',
    '../../Utilities/_Control',
    '../../Utilities/_ElementUtilities',
    '../AppBar/_Icon'
    ], function NavBarCommandInit(exports, _Global, _Base, _ErrorFromName, _Resources, ControlProcessor, Navigation, _Control, _ElementUtilities, _Icon) {
    "use strict";

    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {
        _WinPressed: _Base.Namespace._lazy(function () {
            var WinPressed = _Base.Class.define(function _WinPressed_ctor(element) {
                // WinPressed is the combination of :hover:active
                // :hover is delayed by trident for touch by 300ms so if you want :hover:active to work quickly you need to
                // use this behavior.
                // :active does not bubble to its parent like :hover does so this is also useful for that scenario.
                this._element = element;
                _ElementUtilities._addEventListener(this._element, "pointerdown", this._MSPointerDownButtonHandler.bind(this));
            }, {
                _MSPointerDownButtonHandler: function _WinPressed_MSPointerDownButtonHandler(ev) {
                    if (!this._pointerUpBound) {
                        this._pointerUpBound = this._MSPointerUpHandler.bind(this);
                        this._pointerCancelBound = this._MSPointerCancelHandler.bind(this);
                        this._pointerOverBound = this._MSPointerOverHandler.bind(this);
                        this._pointerOutBound = this._MSPointerOutHandler.bind(this);
                    }

                    if (ev.isPrimary) {
                        if (this._pointerId) {
                            this._resetPointer();
                        }

                        if (!_ElementUtilities._matchesSelector(ev.target, ".win-interactive, .win-interactive *")) {
                            this._pointerId = ev.pointerId;

                            _ElementUtilities._addEventListener(_Global, "pointerup", this._pointerUpBound, true);
                            _ElementUtilities._addEventListener(_Global, "pointercancel", this._pointerCancelBound), true;
                            _ElementUtilities._addEventListener(this._element, "pointerover", this._pointerOverBound, true);
                            _ElementUtilities._addEventListener(this._element, "pointerout", this._pointerOutBound, true);

                            _ElementUtilities.addClass(this._element, WinPressed.winPressed);
                        }
                    }
                },

                _MSPointerOverHandler: function _WinPressed_MSPointerOverHandler(ev) {
                    if (this._pointerId === ev.pointerId) {
                        _ElementUtilities.addClass(this._element, WinPressed.winPressed);
                    }
                },

                _MSPointerOutHandler: function _WinPressed_MSPointerOutHandler(ev) {
                    if (this._pointerId === ev.pointerId) {
                        _ElementUtilities.removeClass(this._element, WinPressed.winPressed);
                    }
                },

                _MSPointerCancelHandler: function _WinPressed_MSPointerCancelHandler(ev) {
                    if (this._pointerId === ev.pointerId) {
                        this._resetPointer();
                    }
                },

                _MSPointerUpHandler: function _WinPressed_MSPointerUpHandler(ev) {
                    if (this._pointerId === ev.pointerId) {
                        this._resetPointer();
                    }
                },

                _resetPointer: function _WinPressed_resetPointer() {
                    this._pointerId = null;

                    _ElementUtilities._removeEventListener(_Global, "pointerup", this._pointerUpBound, true);
                    _ElementUtilities._removeEventListener(_Global, "pointercancel", this._pointerCancelBound, true);
                    _ElementUtilities._removeEventListener(this._element, "pointerover", this._pointerOverBound, true);
                    _ElementUtilities._removeEventListener(this._element, "pointerout", this._pointerOutBound, true);

                    _ElementUtilities.removeClass(this._element, WinPressed.winPressed);
                },

                dispose: function _WinPressed_dispose() {
                    if (this._disposed) {
                        return;
                    }
                    this._disposed = true;

                    this._resetPointer();
                }
            }, {
                winPressed: "win-pressed"
            });

            return WinPressed;
        }),
        /// <field>
        /// <summary locid="WinJS.UI.NavBarCommand">
        /// Represents a navigation command in an NavBarContainer.
        /// </summary>
        /// <compatibleWith platform="Windows" minVersion="8.1"/>
        /// </field>
        /// <icon src="ui_winjs.ui.navbarcommand.12x12.png" width="12" height="12" />
        /// <icon src="ui_winjs.ui.navbarcommand.16x16.png" width="16" height="16" />
        /// <htmlSnippet><![CDATA[<div data-win-control="WinJS.UI.NavBarCommand" data-win-options="{location:'/pages/home/home.html',label:'Home',icon:WinJS.UI.AppBarIcon.home}"></div>]]></htmlSnippet>
        /// <part name="navbarcommand" class="win-navbarcommand" locid="WinJS.UI.NavBarCommand_part:navbarcommand">Styles the entire NavBarCommand control.</part>
        /// <part name="button" class="win-navbarcommand-button" locid="WinJS.UI.NavBarCommand_part:button">Styles the main button in a NavBarCommand.</part>
        /// <part name="splitbutton" class="win-navbarcommand-splitbutton" locid="WinJS.UI.NavBarCommand_part:splitbutton">Styles the split button in a NavBarCommand</part>
        /// <part name="icon" class="win-navbarcommand-icon" locid="WinJS.UI.NavBarCommand_part:icon">Styles the icon in the main button of a NavBarCommand.</part>
        /// <part name="label" class="win-navbarcommand-label" locid="WinJS.UI.NavBarCommand_part:label">Styles the label in the main button of a NavBarCommand.</part>
        /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
        /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
        /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
        NavBarCommand: _Base.Namespace._lazy(function () {
            var Key = _ElementUtilities.Key;

            var strings = {
                get duplicateConstruction() { return "Invalid argument: Controls may only be instantiated one time for each DOM element"; }
            };

            var NavBarCommand = _Base.Class.define(function NavBarCommand_ctor(element, options) {
                /// <signature helpKeyword="WinJS.UI.NavBarCommand.NavBarCommand">
                /// <summary locid="WinJS.UI.NavBarCommand.constructor">
                /// Creates a new NavBarCommand.
                /// </summary>
                /// <param name="element" type="HTMLElement" domElement="true" isOptional="true" locid="WinJS.UI.NavBarCommand.constructor_p:element">
                /// The DOM element that will host the new  NavBarCommand control.
                /// </param>
                /// <param name="options" type="Object" isOptional="true" locid="WinJS.UI.NavBarCommand.constructor_p:options">
                /// An object that contains one or more property/value pairs to apply to the new control.
                /// Each property of the options object corresponds to one of the control's properties or events.
                /// Event names must begin with "on".
                /// </param>
                /// <returns type="WinJS.UI.NavBarCommand" locid="WinJS.UI.NavBarCommand.constructor_returnValue">
                /// The new NavBarCommand.
                /// </returns>
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </signature>
                element = element || _Global.document.createElement("DIV");
                options = options || {};

                if (element.winControl) {
                    throw new _ErrorFromName("WinJS.UI.NavBarCommand.DuplicateConstruction", strings.duplicateConstruction);
                }

                // Attaching JS control to DOM element
                element.winControl = this;
                this._element = element;
                _ElementUtilities.addClass(this.element, NavBarCommand._ClassName.navbarcommand);
                _ElementUtilities.addClass(this.element, "win-disposable");

                this._tooltip = null;
                this._splitOpened = false;
                this._buildDom();
                element.addEventListener('keydown', this._keydownHandler.bind(this));

                _Control.setOptions(this, options);
            }, {
                /// <field type="HTMLElement" domElement="true" hidden="true" locid="WinJS.UI.NavBarCommand.element" helpKeyword="WinJS.UI.NavBarCommand.element">
                /// Gets the DOM element that hosts the NavBarCommand.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                element: {
                    get: function () {
                        return this._element;
                    }
                },

                /// <field type="String" locid="WinJS.UI.NavBarCommand.label" helpKeyword="WinJS.UI.NavBarCommand.label">
                /// Gets or sets the label of the NavBarCommand.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                label: {
                    get: function () {
                        return this._label;
                    },
                    set: function (value) {
                        this._label = value;
                        this._labelEl.textContent = value;
                    }
                },

                /// <field type="String" locid="WinJS.UI.NavBarCommand.tooltip" helpKeyword="WinJS.UI.NavBarCommand.tooltip">
                /// Gets or sets the tooltip of the NavBarCommand.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                tooltip: {
                    get: function () {
                        return this._tooltip;
                    },
                    set: function (value) {
                        this._tooltip = value;
                        if (this._tooltip || this._tooltip === "") {
                            this._element.setAttribute('title', this._tooltip);
                        } else {
                            this._element.removeAttribute('title');
                        }
                    }
                },

                /// <field type="String" locid="WinJS.UI.NavBarCommand.icon" helpKeyword="WinJS.UI.NavBarCommand.icon">
                /// Gets or sets the icon of the NavBarCommand. This value is either one of the values of the AppBarIcon enumeration or the path of a custom PNG file.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                icon: {
                    get: function () {
                        return this._icon;
                    },
                    set: function (value) {
                        this._icon = (_Icon[value] || value);

                        // If the icon's a single character, presume a glyph
                        if (this._icon && this._icon.length === 1) {
                            // Set the glyph
                            this._imageSpan.textContent = this._icon;
                            this._imageSpan.style.backgroundImage = "";
                            this._imageSpan.style.msHighContrastAdjust = "";
                            this._imageSpan.style.display = "";
                        } else if (this._icon && this._icon.length > 1) {
                            // Must be an image, set that
                            this._imageSpan.textContent = "";
                            this._imageSpan.style.backgroundImage = this._icon;
                            this._imageSpan.style.msHighContrastAdjust = "none";
                            this._imageSpan.style.display = "";
                        } else {
                            this._imageSpan.textContent = "";
                            this._imageSpan.style.backgroundImage = "";
                            this._imageSpan.style.msHighContrastAdjust = "";
                            this._imageSpan.style.display = "none";
                        }
                    }
                },

                /// <field type="String" locid="WinJS.UI.NavBarCommand.location" helpKeyword="WinJS.UI.NavBarCommand.location">
                /// Gets or sets the command's target location.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                location: {
                    get: function () {
                        return this._location;
                    },
                    set: function (value) {
                        this._location = value;
                    }
                },

                /// <field type="String" locid="WinJS.UI.NavBarCommand.state" helpKeyword="WinJS.UI.NavBarCommand.state">
                /// Gets or sets the state value used for navigation. The command passes this object to the WinJS.Navigation.navigate function.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                state: {
                    get: function () {
                        return this._state;
                    },
                    set: function (value) {
                        this._state = value;
                    }
                },

                /// <field type="Boolean" locid="WinJS.UI.NavBarCommand.splitButton" helpKeyword="WinJS.UI.NavBarCommand.splitButton">
                /// Gets or sets a value that specifies whether the NavBarCommand has a split button.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                splitButton: {
                    get: function () {
                        return this._split;
                    },
                    set: function (value) {
                        this._split = value;
                        if (this._split) {
                            this._splitButtonEl.style.display = "";
                        } else {
                            this._splitButtonEl.style.display = "none";
                        }
                    }
                },

                /// <field type="Boolean" locid="WinJS.UI.NavBarCommand.splitOpened" hidden="true" helpKeyword="WinJS.UI.NavBarCommand.splitOpened">
                /// Gets or sets a value that specifies whether the split button is open.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                splitOpened: {
                    get: function () {
                        return this._splitOpened;
                    },
                    set: function (value) {
                        if (this._splitOpened !== !!value) {
                            this._toggleSplit();
                        }
                    }
                },

                _toggleSplit: function NavBarCommand_toggleSplit() {
                    this._splitOpened = !this._splitOpened;
                    if (this._splitOpened) {
                        _ElementUtilities.addClass(this._splitButtonEl, NavBarCommand._ClassName.navbarcommandsplitbuttonopened);
                        this._splitButtonEl.setAttribute("aria-expanded", "true");
                    } else {
                        _ElementUtilities.removeClass(this._splitButtonEl, NavBarCommand._ClassName.navbarcommandsplitbuttonopened);
                        this._splitButtonEl.setAttribute("aria-expanded", "false");
                    }
                    this._fireEvent(NavBarCommand._EventName._splitToggle);
                },

                _rtl: {
                    get: function () {
                        return _Global.getComputedStyle(this.element).direction === "rtl";
                    }
                },

                _keydownHandler: function NavBarCommand_keydownHandler(ev) {
                    if (_ElementUtilities._matchesSelector(ev.target, ".win-interactive, .win-interactive *")) {
                        return;
                    }

                    var leftStr = this._rtl ? Key.rightArrow : Key.leftArrow;
                    var rightStr = this._rtl ? Key.leftArrow : Key.rightArrow;

                    if (!ev.altKey && (ev.keyCode === leftStr || ev.keyCode === Key.home || ev.keyCode === Key.end) && ev.target === this._splitButtonEl) {
                        _ElementUtilities._setActive(this._buttonEl);
                        if (ev.keyCode === leftStr) {
                            ev.stopPropagation();
                        }
                        ev.preventDefault();
                    } else if (!ev.altKey && ev.keyCode === rightStr && this.splitButton && (ev.target === this._buttonEl || this._buttonEl.contains(ev.target))) {
                        _ElementUtilities._setActive(this._splitButtonEl);
                        if (ev.keyCode === rightStr) {
                            ev.stopPropagation();
                        }
                        ev.preventDefault();
                    } else if ((ev.keyCode === Key.space || ev.keyCode === Key.enter) && (ev.target === this._buttonEl || this._buttonEl.contains(ev.target))) {
                        if (this.location) {
                            Navigation.navigate(this.location, this.state);
                        }
                        this._fireEvent(NavBarCommand._EventName._invoked);
                    } else if ((ev.keyCode === Key.space || ev.keyCode === Key.enter) && ev.target === this._splitButtonEl) {
                        this._toggleSplit();
                    }
                },

                _getFocusInto: function NavBarCommand_getFocusInto(keyCode) {
                    var leftStr = this._rtl ? Key.rightArrow : Key.leftArrow;
                    if ((keyCode === leftStr) && this.splitButton) {
                        return this._splitButtonEl;
                    } else {
                        return this._buttonEl;
                    }
                },

                _buildDom: function NavBarCommand_buildDom() {
                    var markup =
                        '<div tabindex="0" role="button" class="' + NavBarCommand._ClassName.navbarcommandbutton + '">' +
                            '<div class="' + NavBarCommand._ClassName.navbarcommandbuttoncontent + '">' +
                                '<div class="' + NavBarCommand._ClassName.navbarcommandicon + '"></div>' +
                                '<div class="' + NavBarCommand._ClassName.navbarcommandlabel + '"></div>' +
                            '</div>' +
                        '</div>' +
                        '<div tabindex="-1" aria-expanded="false" class="' + NavBarCommand._ClassName.navbarcommandsplitbutton + '"></div>';
                    this.element.insertAdjacentHTML("afterBegin", markup);

                    this._buttonEl = this.element.firstElementChild;
                    this._buttonPressedBehavior = new exports._WinPressed(this._buttonEl);
                    this._contentEl = this._buttonEl.firstElementChild;
                    this._imageSpan = this._contentEl.firstElementChild;
                    this._imageSpan.style.display = "none";
                    this._labelEl = this._imageSpan.nextElementSibling;
                    this._splitButtonEl = this._buttonEl.nextElementSibling;
                    this._splitButtonPressedBehavior = new exports._WinPressed(this._splitButtonEl);
                    this._splitButtonEl.style.display = "none";

                    _ElementUtilities._ensureId(this._buttonEl);
                    this._splitButtonEl.setAttribute("aria-labelledby", this._buttonEl.id);

                    this._buttonEl.addEventListener("click", this._handleButtonClick.bind(this));

                    var mutationObserver = new _ElementUtilities._MutationObserver(this._splitButtonAriaExpandedPropertyChangeHandler.bind(this));
                    mutationObserver.observe(this._splitButtonEl, { attributes: true, attributeFilter: ["aria-expanded"] });
                    this._splitButtonEl.addEventListener("click", this._handleSplitButtonClick.bind(this));

                    // reparent any other elements.
                    var tempEl = this._splitButtonEl.nextSibling;
                    while (tempEl) {
                        this._buttonEl.insertBefore(tempEl, this._contentEl);
                        if (tempEl.nodeName !== "#text") {
                            ControlProcessor.processAll(tempEl);
                        }
                        tempEl = this._splitButtonEl.nextSibling;
                    }
                },

                _handleButtonClick: function NavBarCommand_handleButtonClick(ev) {
                    var srcElement = ev.target;
                    if (!_ElementUtilities._matchesSelector(srcElement, ".win-interactive, .win-interactive *")) {
                        if (this.location) {
                            Navigation.navigate(this.location, this.state);
                        }
                        this._fireEvent(NavBarCommand._EventName._invoked);
                    }
                },

                _splitButtonAriaExpandedPropertyChangeHandler: function NavBarCommand_splitButtonAriaExpandedPropertyChangeHandler() {
                    if ((this._splitButtonEl.getAttribute("aria-expanded") === "true") !== this._splitOpened) {
                        this._toggleSplit();
                    }
                },

                _handleSplitButtonClick: function NavBarCommand_handleSplitButtonClick() {
                    this._toggleSplit();
                },

                _fireEvent: function NavBarCommand_fireEvent(type, detail) {
                    var event = _Global.document.createEvent("CustomEvent");
                    event.initCustomEvent(type, true, false, detail);
                    this.element.dispatchEvent(event);
                },

                dispose: function NavBarCommand_dispose() {
                    /// <signature helpKeyword="WinJS.UI.NavBarCommand.dispose">
                    /// <summary locid="WinJS.UI.NavBarCommand.dispose">
                    /// Disposes this control.
                    /// </summary>
                    /// <compatibleWith platform="Windows" minVersion="8.1"/>
                    /// </signature>
                    if (this._disposed) {
                        return;
                    }
                    this._disposed = true;

                    this._buttonPressedBehavior.dispose();
                    this._splitButtonPressedBehavior.dispose();
                }
            }, {
                _ClassName: {
                    navbarcommand: "win-navbarcommand",
                    navbarcommandbutton: "win-navbarcommand-button",
                    navbarcommandbuttoncontent: "win-navbarcommand-button-content",
                    navbarcommandsplitbutton: "win-navbarcommand-splitbutton",
                    navbarcommandsplitbuttonopened: "win-navbarcommand-splitbutton-opened",
                    navbarcommandicon: "win-navbarcommand-icon",
                    navbarcommandlabel: "win-navbarcommand-label"
                },
                _EventName: {
                    _invoked: "_invoked",
                    _splitToggle: "_splittoggle"
                }
            });
            _Base.Class.mix(NavBarCommand, _Control.DOMEventMixin);
            return NavBarCommand;
        })
    });

});
// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/NavBar/_Container',[
    'exports',
    '../../Core/_Global',
    '../../Core/_Base',
    '../../Core/_BaseUtils',
    '../../Core/_ErrorFromName',
    '../../Core/_Events',
    '../../Core/_Log',
    '../../Core/_Resources',
    '../../Core/_WriteProfilerMark',
    '../../Animations',
    '../../Animations/_TransitionAnimation',
    '../../BindingList',
    '../../ControlProcessor',
    '../../Navigation',
    '../../Promise',
    '../../Scheduler',
    '../../Utilities/_Control',
    '../../Utilities/_ElementUtilities',
    '../../Utilities/_KeyboardBehavior',
    '../../Utilities/_UI',
    '../AppBar/_Constants',
    '../Repeater',
    './_Command'
    ], function NavBarContainerInit(exports, _Global, _Base, _BaseUtils, _ErrorFromName, _Events, _Log, _Resources, _WriteProfilerMark, Animations, _TransitionAnimation, BindingList, ControlProcessor, Navigation, Promise, Scheduler, _Control, _ElementUtilities, _KeyboardBehavior, _UI, _Constants, Repeater, _Command) {
    "use strict";

    function nobodyHasFocus() {
        return _Global.document.activeElement === null || _Global.document.activeElement === _Global.document.body;
    }

    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {
        /// <field>
        /// <summary locid="WinJS.UI.NavBarContainer">
        /// Contains a group of NavBarCommand objects in a NavBar.
        /// </summary>
        /// <compatibleWith platform="Windows" minVersion="8.1"/>
        /// </field>
        /// <icon src="ui_winjs.ui.navbarcontainer.12x12.png" width="12" height="12" />
        /// <icon src="ui_winjs.ui.navbarcontainer.16x16.png" width="16" height="16" />
        /// <htmlSnippet supportsContent="true"><![CDATA[<div data-win-control="WinJS.UI.NavBarContainer">
        /// <div data-win-control="WinJS.UI.NavBarCommand" data-win-options="{location:'/pages/home/home.html',label:'Home',icon:WinJS.UI.AppBarIcon.home}"></div>
        /// </div>]]></htmlSnippet>
        /// <event name="invoked" locid="WinJS.UI.NavBarContainer_e:invoked">Raised when a NavBarCommand is invoked.</event>
        /// <event name="splittoggle" locid="WinJS.UI.NavBarContainer_e:splittoggle">Raised when the split button on a NavBarCommand is toggled.</event>
        /// <part name="navbarcontainer" class="win-navbarcontainer" locid="WinJS.UI.NavBarContainer_part:navbarcontainer">Styles the entire NavBarContainer control.</part>
        /// <part name="pageindicators" class="win-navbarcontainer-pageindicator-box" locid="WinJS.UI.NavBarContainer_part:pageindicators">
        /// Styles the page indication for the NavBarContainer.
        /// </part>
        /// <part name="indicator" class="win-navbarcontainer-pagination-indicator" locid="WinJS.UI.NavBarContainer_part:indicator">Styles the page indication for each page.</part>
        /// <part name="currentindicator" class="win-navbarcontainer-pagination-indicator-current" locid="WinJS.UI.NavBarContainer_part:currentindicator">
        /// Styles the indication of the current page.
        /// </part>
        /// <part name="items" class="win-navbarcontainer-surface" locid="WinJS.UI.NavBarContainer_part:items">Styles the area that contains items for the NavBarContainer.</part>
        /// <part name="navigationArrow" class="win-navbarcontainer-navarrow" locid="WinJS.UI.NavBarContainer_part:navigationArrow">Styles left and right navigation arrows.</part>
        /// <part name="leftNavigationArrow" class="win-navbarcontainer-navleft" locid="WinJS.UI.NavBarContainer_part:leftNavigationArrow">Styles the left navigation arrow.</part>
        /// <part name="rightNavigationArrow" class="win-navbarcontainer-navright" locid="WinJS.UI.NavBarContainer_part:rightNavigationArrow">Styles the right navigation arrow.</part>
        /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
        /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
        /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
        NavBarContainer: _Base.Namespace._lazy(function () {
            var Key = _ElementUtilities.Key;

            var buttonFadeDelay = 3000;
            var PT_TOUCH = _ElementUtilities._MSPointerEvent.MSPOINTER_TYPE_TOUCH || "touch";
            var MS_MANIPULATION_STATE_STOPPED = 0;

            var createEvent = _Events._createEventProperty;
            var eventNames = {
                invoked: "invoked",
                splittoggle: "splittoggle"
            };

            var strings = {
                get duplicateConstruction() { return "Invalid argument: Controls may only be instantiated one time for each DOM element"; },
                get navBarContainerViewportAriaLabel() { return _Resources._getWinJSString("ui/navBarContainerViewportAriaLabel").value; }
            };

            var NavBarContainer = _Base.Class.define(function NavBarContainer_ctor(element, options) {
                /// <signature helpKeyword="WinJS.UI.NavBarContainer.NavBarContainer">
                /// <summary locid="WinJS.UI.NavBarContainer.constructor">
                /// Creates a new NavBarContainer.
                /// </summary>
                /// <param name="element" type="HTMLElement" domElement="true" isOptional="true" locid="WinJS.UI.NavBarContainer.constructor_p:element">
                /// The DOM element that will host the NavBarContainer control.
                /// </param>
                /// <param name="options" type="Object" isOptional="true" locid="WinJS.UI.NavBarContainer.constructor_p:options">
                /// An object that contains one or more property/value pairs to apply to the new control.
                /// Each property of the options object corresponds to one of the control's properties or events.
                /// Event names must begin with "on".
                /// </param>
                /// <returns type="WinJS.UI.NavBarContainer" locid="WinJS.UI.NavBarContainer.constructor_returnValue">
                /// The new NavBarContainer.
                /// </returns>
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </signature>

                element = element || _Global.document.createElement("DIV");
                this._id = element.id || _ElementUtilities._uniqueID(element);
                this._writeProfilerMark("constructor,StartTM");

                options = options || {};

                if (element.winControl) {
                    throw new _ErrorFromName("WinJS.UI.NavBarContainer.DuplicateConstruction", strings.duplicateConstruction);
                }

                // Attaching JS control to DOM element
                element.winControl = this;
                this._element = element;
                _ElementUtilities.addClass(this.element, NavBarContainer._ClassName.navbarcontainer);
                _ElementUtilities.addClass(this.element, "win-disposable");
                if (!element.getAttribute("tabIndex")) {
                    element.tabIndex = -1;
                }

                this._focusCurrentItemPassivelyBound = this._focusCurrentItemPassively.bind(this);
                this._closeSplitAndResetBound = this._closeSplitAndReset.bind(this);
                this._currentManipulationState = MS_MANIPULATION_STATE_STOPPED;

                this._panningDisabled = !_ElementUtilities._supportsSnapPoints;
                this._fixedSize = false;
                this._maxRows = 1;
                this._sizes = {};

                this._setupTree();

                this._duringConstructor = true;

                this._dataChangingBound = this._dataChanging.bind(this);
                this._dataChangedBound = this._dataChanged.bind(this);

                Navigation.addEventListener('navigated', this._closeSplitAndResetBound);

                // Don't use set options for the properties so we can control the ordering to avoid rendering multiple times.
                this.layout = options.layout || _UI.Orientation.horizontal;
                if (options.maxRows) {
                    this.maxRows = options.maxRows;
                }
                if (options.template) {
                    this.template = options.template;
                }
                if (options.data) {
                    this.data = options.data;
                }
                if (options.fixedSize) {
                    this.fixedSize = options.fixedSize;
                }

                // Events only
                _Control._setOptions(this, options, true);

                this._duringConstructor = false;

                if (options.currentIndex) {
                    this.currentIndex = options.currentIndex;
                }

                    this._updatePageUI();

                Scheduler.schedule(function NavBarContainer_async_initialize() {
                    this._updateAppBarReference();
                }, Scheduler.Priority.normal, this, "WinJS.UI.NavBarContainer_async_initialize");

                this._writeProfilerMark("constructor,StopTM");
            }, {
                /// <field type="HTMLElement" domElement="true" hidden="true" locid="WinJS.UI.NavBarContainer.element" helpKeyword="WinJS.UI.NavBarContainer.element">
                /// Gets the DOM element that hosts the NavBarContainer.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                element: {
                    get: function () {
                        return this._element;
                    }
                },

                /// <field type="Object" locid="WinJS.UI.NavBarContainer.template" helpKeyword="WinJS.UI.NavBarContainer.template" potentialValueSelector="[data-win-control='WinJS.Binding.Template']">
                /// Gets or sets a Template or custom rendering function that defines the HTML of each item within the NavBarContainer.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                template: {
                    get: function () {
                        return this._template;
                    },
                    set: function (value) {
                        this._template = value;
                        if (this._repeater) {
                            var hadFocus = this.element.contains(_Global.document.activeElement);

                            if (!this._duringConstructor) {
                                this._closeSplitIfOpen();
                            }

                            // the repeater's template is wired up to this._render() so just resetting it will rebuild the tree.
                            this._repeater.template = this._repeater.template;

                            if (!this._duringConstructor) {
                                this._measured = false;
                                this._sizes.itemMeasured = false;
                                this._reset();
                                if (hadFocus) {
                                    this._keyboardBehavior._focus(0);
                                }
                            }
                        }
                    }
                },

                _render: function NavBarContainer_render(item) {
                    var navbarCommandEl = _Global.document.createElement('div');

                    var template = this._template;
                    if (template) {
                        if (template.render) {
                            template.render(item, navbarCommandEl);
                        } else if (template.winControl && template.winControl.render) {
                            template.winControl.render(item, navbarCommandEl);
                        } else {
                            navbarCommandEl.appendChild(template(item));
                        }
                    }

                    // Create the NavBarCommand after calling render so that the reparenting in navbarCommand works.
                    var navbarCommand = new _Command.NavBarCommand(navbarCommandEl, item);
                    return navbarCommand._element;
                },

                /// <field type="WinJS.Binding.List" locid="WinJS.UI.NavBarContainer.data" helpKeyword="WinJS.UI.NavBarContainer.data">
                /// Gets or sets the WinJS.Binding.List that provides the NavBarContainer with items to display.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                data: {
                    get: function () {
                        return this._repeater && this._repeater.data;
                    },
                    set: function (value) {
                        if (!value) {
                            value = new BindingList.List();
                        }

                        if (!this._duringConstructor) {
                            this._closeSplitIfOpen();
                        }

                        this._removeDataChangingEvents();
                        this._removeDataChangedEvents();

                        var hadFocus = this.element.contains(_Global.document.activeElement);

                        if (!this._repeater) {
                            this._surfaceEl.innerHTML = "";
                            this._repeater = new Repeater.Repeater(this._surfaceEl, {
                                template: this._render.bind(this)
                            });
                        }

                        this._addDataChangingEvents(value);
                        this._repeater.data = value;
                        this._addDataChangedEvents(value);

                        if (!this._duringConstructor) {
                            this._measured = false;
                            this._sizes.itemMeasured = false;
                            this._reset();
                            if (hadFocus) {
                                this._keyboardBehavior._focus(0);
                            }
                        }
                    }
                },

                /// <field type="Number" integer="true" locid="WinJS.UI.NavBarContainer.maxRows" helpKeyword="WinJS.UI.NavBarContainer.maxRows">
                /// Gets or sets the number of rows allowed to be used before items are placed on additional pages.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                maxRows: {
                    get: function () {
                        return this._maxRows;
                    },
                    set: function (value) {
                        value = (+value === value) ? value : 1;
                        this._maxRows = Math.max(1, value);

                        if (!this._duringConstructor) {
                            this._closeSplitIfOpen();

                            this._measured = false;
                            this._reset();
                        }
                    }
                },

                /// <field type="String" oamOptionsDatatype="WinJS.UI.Orientation" locid="WinJS.UI.NavBarContainer.layout" helpKeyword="WinJS.UI.NavBarContainer.layout">
                /// Gets or sets a value that specifies whether the NavBarContainer has a horizontal or vertical layout. The default is "horizontal".
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                layout: {
                    get: function () {
                        return this._layout;
                    },
                    set: function (value) {
                        if (value === _UI.Orientation.vertical) {
                            this._layout = _UI.Orientation.vertical;
                            _ElementUtilities.removeClass(this.element, NavBarContainer._ClassName.horizontal);
                            _ElementUtilities.addClass(this.element, NavBarContainer._ClassName.vertical);
                        } else {
                            this._layout = _UI.Orientation.horizontal;
                            _ElementUtilities.removeClass(this.element, NavBarContainer._ClassName.vertical);
                            _ElementUtilities.addClass(this.element, NavBarContainer._ClassName.horizontal);
                        }

                        this._viewportEl.style.msScrollSnapType = "";
                        this._zooming = false;

                        if (!this._duringConstructor) {
                            this._measured = false;
                            this._sizes.itemMeasured = false;
                            this._ensureVisible(this._keyboardBehavior.currentIndex, true);
                            this._updatePageUI();
                            this._closeSplitIfOpen();
                        }
                    }
                },

                /// <field type="Number" integer="true" locid="WinJS.UI.NavBarContainer.currentIndex" hidden="true" helpKeyword="WinJS.UI.NavBarContainer.currentIndex">
                /// Gets or sets the index of the current NavBarCommand.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                currentIndex: {
                    get: function () {
                        return this._keyboardBehavior.currentIndex;
                    },
                    set: function (value) {
                        if (value === +value) {
                            var hadFocus = this.element.contains(_Global.document.activeElement);

                            this._keyboardBehavior.currentIndex = value;

                            this._ensureVisible(this._keyboardBehavior.currentIndex, true);

                            if (hadFocus) {
                                this._keyboardBehavior._focus();
                            }
                        }
                    }
                },

                /// <field type="Boolean" locid="WinJS.UI.NavBarContainer.fixedSize" helpKeyword="WinJS.UI.NavBarContainer.fixedSize">
                /// Gets or sets a value that specifies whether child NavBarCommand  objects should be a fixed width when there are multiple pages. A value of true indicates
                /// that the NavBarCommand objects use a fixed width; a value of false indicates that they use a dynamic width.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                fixedSize: {
                    get: function () {
                        return this._fixedSize;
                    },
                    set: function (value) {
                        this._fixedSize = !!value;

                        if (!this._duringConstructor) {
                            this._closeSplitIfOpen();

                            if (!this._measured) {
                                this._measure();
                            } else if (this._surfaceEl.children.length > 0) {
                                this._updateGridStyles();
                            }
                        }
                    }
                },

                /// <field type="Function" locid="WinJS.UI.NavBarContainer.oninvoked" helpKeyword="WinJS.UI.NavBarContainer.oninvoked">
                /// Raised when a NavBarCommand has been invoked.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                oninvoked: createEvent(eventNames.invoked),

                /// <field type="Function" locid="WinJS.UI.NavBarContainer.onsplittoggle" helpKeyword="WinJS.UI.NavBarContainer.onsplittoggle">
                /// Raised when the split button on a NavBarCommand is toggled.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                onsplittoggle: createEvent(eventNames.splittoggle),

                forceLayout: function NavBarContainer_forceLayout() {
                    /// <signature helpKeyword="WinJS.UI.NavBarContainer.forceLayout">
                    /// <summary locid="WinJS.UI.NavBarContainer.forceLayout">
                    /// Forces the NavBarContainer to update scroll positions and if the NavBar has changed size, it will also re-measure.
                    /// Use this function when making the NavBarContainer visible again after you set its style.display property to "none".
                    /// </summary>
                    /// <compatibleWith platform="Windows" minVersion="8.1"/>
                    /// </signature>
                    this._resizeHandler();
                    if (this._measured) {
                        this._scrollPosition = _ElementUtilities.getScrollPosition(this._viewportEl)[(this.layout === _UI.Orientation.horizontal ? "scrollLeft" : "scrollTop")];
                    }

                    this._duringForceLayout = true;
                    this._ensureVisible(this._keyboardBehavior.currentIndex, true);
                    this._updatePageUI();
                    this._duringForceLayout = false;
                },

                _updateAppBarReference: function NavBarContainer_updateAppBarReference() {
                    if (!this._appBarEl || !this._appBarEl.contains(this.element)) {
                        if (this._appBarEl) {
                            this._appBarEl.removeEventListener('beforeshow', this._closeSplitAndResetBound);
                            this._appBarEl.removeEventListener('beforeshow', this._resizeImplBound);
                            this._appBarEl.removeEventListener('aftershow', this._focusCurrentItemPassivelyBound);
                        }

                        var appBarEl = this.element.parentNode;
                        while (appBarEl && !_ElementUtilities.hasClass(appBarEl, _Constants.appBarClass)) {
                            appBarEl = appBarEl.parentNode;
                        }
                        this._appBarEl = appBarEl;

                        if (this._appBarEl) {
                            this._appBarEl.addEventListener('beforeshow', this._closeSplitAndResetBound);
                            this._appBarEl.addEventListener('aftershow', this._focusCurrentItemPassivelyBound);
                        }
                    }
                },

                _closeSplitAndReset: function NavBarContainer_closeSplitAndReset() {
                    this._closeSplitIfOpen();
                    this._reset();
                },

                _dataChanging: function NavBarContainer_dataChanging(ev) {
                    // Store the element that was active so that we can detect
                    // if the focus went away because of the data change.
                    this._elementHadFocus = _Global.document.activeElement;

                    if (this._currentSplitNavItem && this._currentSplitNavItem.splitOpened) {
                        if (ev.type === "itemremoved") {
                            if (this._surfaceEl.children[ev.detail.index].winControl === this._currentSplitNavItem) {
                                this._closeSplitIfOpen();
                            }
                        } else if (ev.type === "itemchanged") {
                            if (this._surfaceEl.children[ev.detail.index].winControl === this._currentSplitNavItem) {
                                this._closeSplitIfOpen();
                            }
                        } else if (ev.type === "itemmoved") {
                            if (this._surfaceEl.children[ev.detail.oldIndex].winControl === this._currentSplitNavItem) {
                                this._closeSplitIfOpen();
                            }
                        } else if (ev.type === "reload") {
                            this._closeSplitIfOpen();
                        }
                    }
                },

                _dataChanged: function NavBarContainer_dataChanged(ev) {
                    this._measured = false;

                    if (ev.type === "itemremoved") {
                        if (ev.detail.index < this._keyboardBehavior.currentIndex) {
                            this._keyboardBehavior.currentIndex--;
                        } else if (ev.detail.index === this._keyboardBehavior.currentIndex) {
                            // This clamps if the item being removed was the last item in the list
                            this._keyboardBehavior.currentIndex = this._keyboardBehavior.currentIndex;
                            if (nobodyHasFocus() && this._elementHadFocus) {
                                this._keyboardBehavior._focus();
                            }
                        }
                    } else if (ev.type === "itemchanged") {
                        if (ev.detail.index === this._keyboardBehavior.currentIndex) {
                            if (nobodyHasFocus() && this._elementHadFocus) {
                                this._keyboardBehavior._focus();
                            }
                        }
                    } else if (ev.type === "iteminserted") {
                        if (ev.detail.index <= this._keyboardBehavior.currentIndex) {
                            this._keyboardBehavior.currentIndex++;
                        }
                    } else if (ev.type === "itemmoved") {
                        if (ev.detail.oldIndex === this._keyboardBehavior.currentIndex) {
                            this._keyboardBehavior.currentIndex = ev.detail.newIndex;
                            if (nobodyHasFocus() && this._elementHadFocus) {
                                this._keyboardBehavior._focus();
                            }
                        }
                    } else if (ev.type === "reload") {
                        this._keyboardBehavior.currentIndex = 0;
                        if (nobodyHasFocus() && this._elementHadFocus) {
                            this._keyboardBehavior._focus();
                        }
                    }

                    this._ensureVisible(this._keyboardBehavior.currentIndex, true);
                    this._updatePageUI();
                },

                _focusCurrentItemPassively: function NavBarContainer_focusCurrentItemPassively() {
                    if (this.element.contains(_Global.document.activeElement)) {
                        this._keyboardBehavior._focus();
                    }
                },

                _reset: function NavBarContainer_reset() {
                    this._keyboardBehavior.currentIndex = 0;

                    if (this.element.contains(_Global.document.activeElement)) {
                        this._keyboardBehavior._focus(0);
                    }

                    this._viewportEl.style.msScrollSnapType = "";
                    this._zooming = false;

                    this._ensureVisible(0, true);
                    this._updatePageUI();
                },

                _removeDataChangedEvents: function NavBarContainer_removeDataChangedEvents() {
                    if (this._repeater) {
                        this._repeater.data.removeEventListener("itemchanged", this._dataChangedBound);
                        this._repeater.data.removeEventListener("iteminserted", this._dataChangedBound);
                        this._repeater.data.removeEventListener("itemmoved", this._dataChangedBound);
                        this._repeater.data.removeEventListener("itemremoved", this._dataChangedBound);
                        this._repeater.data.removeEventListener("reload", this._dataChangedBound);
                    }
                },

                _addDataChangedEvents: function NavBarContainer_addDataChangedEvents() {
                    if (this._repeater) {
                        this._repeater.data.addEventListener("itemchanged", this._dataChangedBound);
                        this._repeater.data.addEventListener("iteminserted", this._dataChangedBound);
                        this._repeater.data.addEventListener("itemmoved", this._dataChangedBound);
                        this._repeater.data.addEventListener("itemremoved", this._dataChangedBound);
                        this._repeater.data.addEventListener("reload", this._dataChangedBound);
                    }
                },

                _removeDataChangingEvents: function NavBarContainer_removeDataChangingEvents() {
                    if (this._repeater) {
                        this._repeater.data.removeEventListener("itemchanged", this._dataChangingBound);
                        this._repeater.data.removeEventListener("iteminserted", this._dataChangingBound);
                        this._repeater.data.removeEventListener("itemmoved", this._dataChangingBound);
                        this._repeater.data.removeEventListener("itemremoved", this._dataChangingBound);
                        this._repeater.data.removeEventListener("reload", this._dataChangingBound);
                    }
                },

                _addDataChangingEvents: function NavBarContainer_addDataChangingEvents(bindingList) {
                    bindingList.addEventListener("itemchanged", this._dataChangingBound);
                    bindingList.addEventListener("iteminserted", this._dataChangingBound);
                    bindingList.addEventListener("itemmoved", this._dataChangingBound);
                    bindingList.addEventListener("itemremoved", this._dataChangingBound);
                    bindingList.addEventListener("reload", this._dataChangingBound);
                },

                _mouseleave: function NavBarContainer_mouseleave() {
                    if (this._mouseInViewport) {
                        this._mouseInViewport = false;
                        this._updateArrows();
                    }
                },

                _MSPointerDown: function NavBarContainer_MSPointerDown(ev) {
                    if (ev.pointerType === PT_TOUCH) {
                        if (this._mouseInViewport) {
                            this._mouseInViewport = false;
                            this._updateArrows();
                        }
                    }
                },

                _MSPointerMove: function NavBarContainer_MSPointerMove(ev) {
                    if (ev.pointerType !== PT_TOUCH) {
                        if (!this._mouseInViewport) {
                            this._mouseInViewport = true;
                            this._updateArrows();
                        }
                    }
                },

                _setupTree: function NavBarContainer_setupTree() {
                    this._animateNextPreviousButtons = Promise.wrap();
                    this._element.addEventListener('mouseleave', this._mouseleave.bind(this));
                    _ElementUtilities._addEventListener(this._element, 'pointerdown', this._MSPointerDown.bind(this));
                    _ElementUtilities._addEventListener(this._element, 'pointermove', this._MSPointerMove.bind(this));
                    _ElementUtilities._addEventListener(this._element, "focusin", this._focusHandler.bind(this), false);

                    this._pageindicatorsEl = _Global.document.createElement('div');
                    _ElementUtilities.addClass(this._pageindicatorsEl, NavBarContainer._ClassName.pageindicators);
                    this._element.appendChild(this._pageindicatorsEl);

                    this._ariaStartMarker = _Global.document.createElement("div");
                    this._element.appendChild(this._ariaStartMarker);

                    this._viewportEl = _Global.document.createElement('div');
                    _ElementUtilities.addClass(this._viewportEl, NavBarContainer._ClassName.viewport);
                    this._element.appendChild(this._viewportEl);
                    this._viewportEl.setAttribute("role", "group");
                    this._viewportEl.setAttribute("aria-label", strings.navBarContainerViewportAriaLabel);

                    this._boundResizeHandler = this._resizeHandler.bind(this);
                    _ElementUtilities._resizeNotifier.subscribe(this._element, this._boundResizeHandler);
                    this._viewportEl.addEventListener("mselementresize", this._resizeHandler.bind(this));
                    this._viewportEl.addEventListener("scroll", this._scrollHandler.bind(this));
                    this._viewportEl.addEventListener("MSManipulationStateChanged", this._MSManipulationStateChangedHandler.bind(this));

                    this._ariaEndMarker = _Global.document.createElement("div");
                    this._element.appendChild(this._ariaEndMarker);

                    this._surfaceEl = _Global.document.createElement('div');
                    _ElementUtilities.addClass(this._surfaceEl, NavBarContainer._ClassName.surface);
                    this._viewportEl.appendChild(this._surfaceEl);

                    this._surfaceEl.addEventListener("_invoked", this._navbarCommandInvokedHandler.bind(this));
                    this._surfaceEl.addEventListener("_splittoggle", this._navbarCommandSplitToggleHandler.bind(this));
                    _ElementUtilities._addEventListener(this._surfaceEl, "focusin", this._itemsFocusHandler.bind(this), false);
                    this._surfaceEl.addEventListener("keydown", this._keyDownHandler.bind(this));

                    // Reparent NavBarCommands which were in declarative markup
                    var tempEl = this.element.firstElementChild;
                    while (tempEl !== this._pageindicatorsEl) {
                        this._surfaceEl.appendChild(tempEl);
                        ControlProcessor.process(tempEl);
                        tempEl = this.element.firstElementChild;
                    }

                    this._leftArrowEl = _Global.document.createElement('div');
                    _ElementUtilities.addClass(this._leftArrowEl, NavBarContainer._ClassName.navleftarrow);
                    _ElementUtilities.addClass(this._leftArrowEl, NavBarContainer._ClassName.navarrow);
                    this._element.appendChild(this._leftArrowEl);
                    this._leftArrowEl.addEventListener('click', this._goLeft.bind(this));
                    this._leftArrowEl.style.opacity = 0;
                    this._leftArrowEl.style.visibility = 'hidden';
                    this._leftArrowFadeOut = Promise.wrap();

                    this._rightArrowEl = _Global.document.createElement('div');
                    _ElementUtilities.addClass(this._rightArrowEl, NavBarContainer._ClassName.navrightarrow);
                    _ElementUtilities.addClass(this._rightArrowEl, NavBarContainer._ClassName.navarrow);
                    this._element.appendChild(this._rightArrowEl);
                    this._rightArrowEl.addEventListener('click', this._goRight.bind(this));
                    this._rightArrowEl.style.opacity = 0;
                    this._rightArrowEl.style.visibility = 'hidden';
                    this._rightArrowFadeOut = Promise.wrap();

                    this._keyboardBehavior = new _KeyboardBehavior._KeyboardBehavior(this._surfaceEl, {
                        scroller: this._viewportEl
                    });
                    this._winKeyboard = new _KeyboardBehavior._WinKeyboard(this._surfaceEl);
                },

                _goRight: function NavBarContainer_goRight() {
                    if (this._sizes.rtl) {
                        this._goPrev();
                    } else {
                        this._goNext();
                    }
                },

                _goLeft: function NavBarContainer_goLeft() {
                    if (this._sizes.rtl) {
                        this._goNext();
                    } else {
                        this._goPrev();
                    }
                },

                _goNext: function NavBarContainer_goNext() {
                    this._measure();
                    var itemsPerPage = this._sizes.rowsPerPage * this._sizes.columnsPerPage;
                    var targetPage = Math.min(Math.floor(this._keyboardBehavior.currentIndex / itemsPerPage) + 1, this._sizes.pages - 1);
                    this._keyboardBehavior.currentIndex = Math.min(itemsPerPage * targetPage, this._surfaceEl.children.length);
                    this._keyboardBehavior._focus();
                },

                _goPrev: function NavBarContainer_goPrev() {
                    this._measure();
                    var itemsPerPage = this._sizes.rowsPerPage * this._sizes.columnsPerPage;
                    var targetPage = Math.max(0, Math.floor(this._keyboardBehavior.currentIndex / itemsPerPage) - 1);
                    this._keyboardBehavior.currentIndex = Math.max(itemsPerPage * targetPage, 0);
                    this._keyboardBehavior._focus();
                },

                _currentPage: {
                    get: function () {
                        if (this.layout === _UI.Orientation.horizontal) {
                            this._measure();
                            if (this._sizes.viewportOffsetWidth > 0) {
                                return Math.min(this._sizes.pages - 1, Math.round(this._scrollPosition / this._sizes.viewportOffsetWidth));
                            }
                        }
                        return 0;
                    }
                },

                _resizeHandler: function NavBarContainer_resizeHandler() {
                    if (this._disposed) { return; }
                    if (!this._measured) { return; }
                    var viewportResized = this.layout === _UI.Orientation.horizontal
                            ? this._sizes.viewportOffsetWidth !== parseFloat(_Global.getComputedStyle(this._viewportEl).width)
                            : this._sizes.viewportOffsetHeight !== parseFloat(_Global.getComputedStyle(this._viewportEl).height);
                    if (!viewportResized) { return; }

                    this._measured = false;

                    if (!this._pendingResize) {
                        this._pendingResize = true;

                        this._resizeImplBound = this._resizeImplBound || this._resizeImpl.bind(this);

                        this._updateAppBarReference();

                        if (this._appBarEl && this._appBarEl.winControl && this._appBarEl.winControl.hidden) {
                            // Do resize lazily.
                            Scheduler.schedule(this._resizeImplBound, Scheduler.Priority.idle, null, "WinJS.UI.NavBarContainer._resizeImpl");
                            this._appBarEl.addEventListener('beforeshow', this._resizeImplBound);
                        } else {
                            // Do resize now
                            this._resizeImpl();
                        }
                    }
                },

                _resizeImpl: function NavBarContainer_resizeImpl() {
                    if (!this._disposed && this._pendingResize) {
                        this._pendingResize = false;
                        if (this._appBarEl) {
                            this._appBarEl.removeEventListener('beforeshow', this._resizeImplBound);
                        }

                        this._keyboardBehavior.currentIndex = 0;
                        if (this.element.contains(_Global.document.activeElement)) {
                            this._keyboardBehavior._focus(this._keyboardBehavior.currentIndex);
                        }
                        this._closeSplitIfOpen();
                        this._ensureVisible(this._keyboardBehavior.currentIndex, true);
                        this._updatePageUI();
                    }
                },

                _keyDownHandler: function NavBarContainer_keyDownHandler(ev) {
                    var keyCode = ev.keyCode;
                    if (!ev.altKey && (keyCode === Key.pageUp || keyCode === Key.pageDown)) {
                        var srcElement = ev.target;
                        if (_ElementUtilities._matchesSelector(srcElement, ".win-interactive, .win-interactive *")) {
                            return;
                        }

                        var index = this._keyboardBehavior.currentIndex;
                        this._measure();

                        var sizes = this._sizes;
                        var page = Math.floor(index / (sizes.columnsPerPage * sizes.rowsPerPage));

                        var scrollPositionTarget = null;
                        if (keyCode === Key.pageUp) {
                            if (this.layout === _UI.Orientation.horizontal) {
                                var indexOfFirstItemOnPage = page * sizes.columnsPerPage * sizes.rowsPerPage;
                                if (index === indexOfFirstItemOnPage && this._surfaceEl.children[index].winControl._buttonEl === _Global.document.activeElement) {
                                    // First item on page so go back 1 page.
                                    index = index - sizes.columnsPerPage * sizes.rowsPerPage;
                                } else {
                                    // Not first item on page so go to the first item on page.
                                    index = indexOfFirstItemOnPage;
                                }
                            } else {
                                var currentItem = this._surfaceEl.children[index];
                                var top = currentItem.offsetTop;
                                var bottom = top + currentItem.offsetHeight;
                                var scrollPosition = this._zooming ? this._zoomPosition : this._scrollPosition;

                                if (top >= scrollPosition && bottom < scrollPosition + sizes.viewportOffsetHeight) {
                                    // current item is fully on screen.
                                    while (index > 0 &&
                                        this._surfaceEl.children[index - 1].offsetTop > scrollPosition) {
                                        index--;
                                    }
                                }

                                if (this._keyboardBehavior.currentIndex === index) {
                                    var scrollPositionForOnePageAboveItem = bottom - sizes.viewportOffsetHeight;
                                    index = Math.max(0, index - 1);
                                    while (index > 0 &&
                                        this._surfaceEl.children[index - 1].offsetTop > scrollPositionForOnePageAboveItem) {
                                        index--;
                                    }
                                    if (index > 0) {
                                        scrollPositionTarget = this._surfaceEl.children[index].offsetTop - this._sizes.itemMarginTop;
                                    } else {
                                        scrollPositionTarget = 0;
                                    }
                                }
                            }

                            index = Math.max(index, 0);
                            this._keyboardBehavior.currentIndex = index;

                            var element = this._surfaceEl.children[index].winControl._buttonEl;

                            if (scrollPositionTarget !== null) {
                                this._scrollTo(scrollPositionTarget);
                            }

                            _ElementUtilities._setActive(element, this._viewportEl);
                        } else {
                            if (this.layout === _UI.Orientation.horizontal) {
                                var indexOfLastItemOnPage = (page + 1) * sizes.columnsPerPage * sizes.rowsPerPage - 1;

                                if (index === indexOfLastItemOnPage) {
                                    // Last item on page so go forward 1 page.
                                    index = index + sizes.columnsPerPage * sizes.rowsPerPage;
                                } else {
                                    // Not Last item on page so go to last item on page.
                                    index = indexOfLastItemOnPage;
                                }
                            } else {
                                var currentItem = this._surfaceEl.children[this._keyboardBehavior.currentIndex];
                                var top = currentItem.offsetTop;
                                var bottom = top + currentItem.offsetHeight;
                                var scrollPosition = this._zooming ? this._zoomPosition : this._scrollPosition;

                                if (top >= scrollPosition && bottom < scrollPosition + sizes.viewportOffsetHeight) {
                                    // current item is fully on screen.
                                    while (index < this._surfaceEl.children.length - 1 &&
                                        this._surfaceEl.children[index + 1].offsetTop + this._surfaceEl.children[index + 1].offsetHeight < scrollPosition + sizes.viewportOffsetHeight) {
                                        index++;
                                    }
                                }

                                if (index === this._keyboardBehavior.currentIndex) {
                                    var scrollPositionForOnePageBelowItem = top + sizes.viewportOffsetHeight;
                                    index = Math.min(this._surfaceEl.children.length - 1, index + 1);
                                    while (index < this._surfaceEl.children.length - 1 &&
                                        this._surfaceEl.children[index + 1].offsetTop + this._surfaceEl.children[index + 1].offsetHeight < scrollPositionForOnePageBelowItem) {
                                        index++;
                                    }

                                    if (index < this._surfaceEl.children.length - 1) {
                                        scrollPositionTarget = this._surfaceEl.children[index + 1].offsetTop - this._sizes.viewportOffsetHeight;
                                    } else {
                                        scrollPositionTarget = this._scrollLength - this._sizes.viewportOffsetHeight;
                                    }
                                }
                            }

                            index = Math.min(index, this._surfaceEl.children.length - 1);
                            this._keyboardBehavior.currentIndex = index;

                            var element = this._surfaceEl.children[index].winControl._buttonEl;

                            if (scrollPositionTarget !== null) {
                                this._scrollTo(scrollPositionTarget);
                            }

                            try {
                                _ElementUtilities._setActive(element, this._viewportEl);
                            } catch (e) {
                            }
                        }
                    }
                },

                _focusHandler: function NavBarContainer_focusHandler(ev) {
                    var srcElement = ev.target;
                    if (!this._surfaceEl.contains(srcElement)) {
                        // Forward focus from NavBarContainer, viewport or surface to the currentIndex.
                        this._skipEnsureVisible = true;
                        this._keyboardBehavior._focus(this._keyboardBehavior.currentIndex);
                    }
                },

                _itemsFocusHandler: function NavBarContainer_itemsFocusHandler(ev) {
                    // Find the item which is being focused and scroll it to view.
                    var srcElement = ev.target;
                    if (srcElement === this._surfaceEl) {
                        return;
                    }

                    while (srcElement.parentNode !== this._surfaceEl) {
                        srcElement = srcElement.parentNode;
                    }

                    var index = -1;
                    while (srcElement) {
                        index++;
                        srcElement = srcElement.previousSibling;
                    }

                    if (this._skipEnsureVisible) {
                        this._skipEnsureVisible = false;
                    } else {
                        this._ensureVisible(index);
                    }
                },

                _ensureVisible: function NavBarContainer_ensureVisible(index, withoutAnimation) {
                    this._measure();

                    if (this.layout === _UI.Orientation.horizontal) {
                        var page = Math.floor(index / (this._sizes.rowsPerPage * this._sizes.columnsPerPage));
                        this._scrollTo(page * this._sizes.viewportOffsetWidth, withoutAnimation);
                    } else {
                        var element = this._surfaceEl.children[index];
                        var maxScrollPosition;
                        if (index > 0) {
                            maxScrollPosition = element.offsetTop - this._sizes.itemMarginTop;
                        } else {
                            maxScrollPosition = 0;
                        }
                        var minScrollPosition;
                        if (index < this._surfaceEl.children.length - 1) {
                            minScrollPosition = this._surfaceEl.children[index + 1].offsetTop - this._sizes.viewportOffsetHeight;
                        } else {
                            minScrollPosition = this._scrollLength - this._sizes.viewportOffsetHeight;
                        }

                        var newScrollPosition = this._zooming ? this._zoomPosition : this._scrollPosition;
                        newScrollPosition = Math.max(newScrollPosition, minScrollPosition);
                        newScrollPosition = Math.min(newScrollPosition, maxScrollPosition);
                        this._scrollTo(newScrollPosition, withoutAnimation);
                    }
                },

                _scrollTo: function NavBarContainer_scrollTo(targetScrollPosition, withoutAnimation) {
                    this._measure();
                    if (this.layout === _UI.Orientation.horizontal) {
                        targetScrollPosition = Math.max(0, Math.min(this._scrollLength - this._sizes.viewportOffsetWidth, targetScrollPosition));
                    } else {
                        targetScrollPosition = Math.max(0, Math.min(this._scrollLength - this._sizes.viewportOffsetHeight, targetScrollPosition));
                    }

                    if (withoutAnimation) {
                        if (Math.abs(this._scrollPosition - targetScrollPosition) > 1) {
                            this._zooming = false;

                            this._scrollPosition = targetScrollPosition;
                            this._updatePageUI();
                            if (!this._duringForceLayout) {
                                this._closeSplitIfOpen();
                            }

                            var newScrollPos = {};
                            newScrollPos[(this.layout === _UI.Orientation.horizontal ? "scrollLeft" : "scrollTop")] = targetScrollPosition;
                            _ElementUtilities.setScrollPosition(this._viewportEl, newScrollPos);
                        }
                    } else {
                        if ((!this._zooming && Math.abs(this._scrollPosition - targetScrollPosition) > 1) || (this._zooming && Math.abs(this._zoomPosition - targetScrollPosition) > 1)) {
                            this._zoomPosition = targetScrollPosition;

                            this._zooming = true;

                            if (this.layout === _UI.Orientation.horizontal) {
                                this._viewportEl.style.msScrollSnapType = "none";
                                _ElementUtilities._zoomTo(this._viewportEl, { contentX: targetScrollPosition, contentY: 0, viewportX: 0, viewportY: 0 });
                            } else {
                                _ElementUtilities._zoomTo(this._viewportEl, { contentX: 0, contentY: targetScrollPosition, viewportX: 0, viewportY: 0 });
                            }

                            this._closeSplitIfOpen();
                        }
                    }
                },

                _MSManipulationStateChangedHandler: function NavBarContainer_MSManipulationStateChangedHandler(e) {
                    this._currentManipulationState = e.currentState;

                    if (e.currentState === e.MS_MANIPULATION_STATE_ACTIVE) {
                        this._viewportEl.style.msScrollSnapType = "";
                        this._zooming = false;
                    }

                    _Global.clearTimeout(this._manipulationStateTimeoutId);
                    // The extra stop event is firing when an zoomTo is called during another zoomTo and
                    // also the first zoomTo after a resize.
                    if (e.currentState === e.MS_MANIPULATION_STATE_STOPPED) {
                        this._manipulationStateTimeoutId = _Global.setTimeout(function () {
                            this._viewportEl.style.msScrollSnapType = "";
                            this._zooming = false;
                            this._updateCurrentIndexIfPageChanged();
                        }.bind(this), 100);
                    }
                },

                _scrollHandler: function NavBarContainer_scrollHandler() {
                    if (this._disposed) { return; }

                    this._measured = false;
                    if (!this._checkingScroll) {
                        var that = this;
                        this._checkingScroll = _BaseUtils._requestAnimationFrame(function () {
                            if (that._disposed) { return; }
                            that._checkingScroll = null;

                            var newScrollPosition = _ElementUtilities.getScrollPosition(that._viewportEl)[(that.layout === _UI.Orientation.horizontal ? "scrollLeft" : "scrollTop")];
                            if (newScrollPosition !== that._scrollPosition) {
                                that._scrollPosition = newScrollPosition;
                                that._closeSplitIfOpen();
                            }
                            that._updatePageUI();

                            if (!that._zooming && that._currentManipulationState === MS_MANIPULATION_STATE_STOPPED) {
                                that._updateCurrentIndexIfPageChanged();
                            }
                        });
                    }
                },

                _updateCurrentIndexIfPageChanged: function NavBarContainer_updateCurrentIndexIfPageChanged() {
                    // If you change pages via pagination arrows, mouse wheel, or panning we need to update the current
                    // item to be the first item on the new page.
                    if (this.layout === _UI.Orientation.horizontal) {
                        this._measure();
                        var currentPage = this._currentPage;
                        var firstIndexOnPage = currentPage * this._sizes.rowsPerPage * this._sizes.columnsPerPage;
                        var lastIndexOnPage = (currentPage + 1) * this._sizes.rowsPerPage * this._sizes.columnsPerPage - 1;

                        if (this._keyboardBehavior.currentIndex < firstIndexOnPage || this._keyboardBehavior.currentIndex > lastIndexOnPage) {
                            // Page change occurred.
                            this._keyboardBehavior.currentIndex = firstIndexOnPage;

                            if (this.element.contains(_Global.document.activeElement)) {
                                this._keyboardBehavior._focus(this._keyboardBehavior.currentIndex);
                            }
                        }
                    }
                },

                _measure: function NavBarContainer_measure() {
                    if (!this._measured) {
                        this._resizeImpl();
                        this._writeProfilerMark("measure,StartTM");

                        var sizes = this._sizes;

                        sizes.rtl = _Global.getComputedStyle(this._element).direction === "rtl";

                        var itemCount = this._surfaceEl.children.length;
                        if (itemCount > 0) {
                            if (!this._sizes.itemMeasured) {
                                this._writeProfilerMark("measureItem,StartTM");

                                var elementToMeasure = this._surfaceEl.firstElementChild;
                                // Clear inline margins set by NavBarContainer before measuring.
                                elementToMeasure.style.margin = "";
                                elementToMeasure.style.width = "";
                                var elementComputedStyle = _Global.getComputedStyle(elementToMeasure);
                                sizes.itemOffsetWidth = parseFloat(_Global.getComputedStyle(elementToMeasure).width);
                                if (elementToMeasure.offsetWidth === 0) {
                                    sizes.itemOffsetWidth = 0;
                                }
                                sizes.itemMarginLeft = parseFloat(elementComputedStyle.marginLeft);
                                sizes.itemMarginRight = parseFloat(elementComputedStyle.marginRight);
                                sizes.itemWidth = sizes.itemOffsetWidth + sizes.itemMarginLeft + sizes.itemMarginRight;
                                sizes.itemOffsetHeight = parseFloat(_Global.getComputedStyle(elementToMeasure).height);
                                if (elementToMeasure.offsetHeight === 0) {
                                    sizes.itemOffsetHeight = 0;
                                }
                                sizes.itemMarginTop = parseFloat(elementComputedStyle.marginTop);
                                sizes.itemMarginBottom = parseFloat(elementComputedStyle.marginBottom);
                                sizes.itemHeight = sizes.itemOffsetHeight + sizes.itemMarginTop + sizes.itemMarginBottom;
                                if (sizes.itemOffsetWidth > 0 && sizes.itemOffsetHeight > 0) {
                                    sizes.itemMeasured = true;
                                }
                                this._writeProfilerMark("measureItem,StopTM");
                            }

                            sizes.viewportOffsetWidth = parseFloat(_Global.getComputedStyle(this._viewportEl).width);
                            if (this._viewportEl.offsetWidth === 0) {
                                sizes.viewportOffsetWidth = 0;
                            }
                            sizes.viewportOffsetHeight = parseFloat(_Global.getComputedStyle(this._viewportEl).height);
                            if (this._viewportEl.offsetHeight === 0) {
                                sizes.viewportOffsetHeight = 0;
                            }

                            if (sizes.viewportOffsetWidth === 0 || sizes.itemOffsetHeight === 0) {
                                this._measured = false;
                            } else {
                                this._measured = true;
                            }

                            if (this.layout === _UI.Orientation.horizontal) {
                                this._scrollPosition = _ElementUtilities.getScrollPosition(this._viewportEl).scrollLeft;

                                sizes.leadingEdge = this._leftArrowEl.offsetWidth + parseInt(_Global.getComputedStyle(this._leftArrowEl).marginLeft) + parseInt(_Global.getComputedStyle(this._leftArrowEl).marginRight);
                                var usableSpace = sizes.viewportOffsetWidth - sizes.leadingEdge * 2;
                                sizes.maxColumns = sizes.itemWidth ? Math.max(1, Math.floor(usableSpace / sizes.itemWidth)) : 1;
                                sizes.rowsPerPage = Math.min(this.maxRows, Math.ceil(itemCount / sizes.maxColumns));
                                sizes.columnsPerPage = Math.min(sizes.maxColumns, itemCount);
                                sizes.pages = Math.ceil(itemCount / (sizes.columnsPerPage * sizes.rowsPerPage));
                                sizes.trailingEdge = sizes.leadingEdge;
                                sizes.extraSpace = usableSpace - (sizes.columnsPerPage * sizes.itemWidth);

                                this._scrollLength = sizes.viewportOffsetWidth * sizes.pages;

                                this._keyboardBehavior.fixedSize = sizes.rowsPerPage;
                                this._keyboardBehavior.fixedDirection = _KeyboardBehavior._KeyboardBehavior.FixedDirection.height;

                                this._surfaceEl.style.height = (sizes.itemHeight * sizes.rowsPerPage) + "px";
                                this._surfaceEl.style.width = this._scrollLength + "px";
                            } else {
                                this._scrollPosition = this._viewportEl.scrollTop;

                                sizes.leadingEdge = 0;
                                sizes.rowsPerPage = itemCount;
                                sizes.columnsPerPage = 1;
                                sizes.pages = 1;
                                sizes.trailingEdge = 0;

                                // Reminder there is margin collapsing so just use scrollHeight instead of itemHeight * itemCount
                                this._scrollLength = this._viewportEl.scrollHeight;

                                this._keyboardBehavior.fixedSize = sizes.columnsPerPage;
                                this._keyboardBehavior.fixedDirection = _KeyboardBehavior._KeyboardBehavior.FixedDirection.width;

                                this._surfaceEl.style.height = "";
                                this._surfaceEl.style.width = "";
                            }

                            this._updateGridStyles();
                        } else {
                            sizes.pages = 1;
                            this._hasPreviousContent = false;
                            this._hasNextContent = false;
                            this._surfaceEl.style.height = "";
                            this._surfaceEl.style.width = "";
                        }

                        this._writeProfilerMark("measure,StopTM");
                    }
                },

                _updateGridStyles: function NavBarContainer_updateGridStyles() {
                    var sizes = this._sizes;
                    var itemCount = this._surfaceEl.children.length;

                    for (var index = 0; index < itemCount; index++) {
                        var itemEl = this._surfaceEl.children[index];

                        var marginRight;
                        var marginLeft;
                        var width = "";

                        if (this.layout === _UI.Orientation.horizontal) {
                            var column = Math.floor(index / sizes.rowsPerPage);
                            var isFirstColumnOnPage = column % sizes.columnsPerPage === 0;
                            var isLastColumnOnPage = column % sizes.columnsPerPage === sizes.columnsPerPage - 1;

                            var extraTrailingMargin = sizes.trailingEdge;
                            if (this.fixedSize) {
                                extraTrailingMargin += sizes.extraSpace;
                            } else {
                                var spaceToDistribute = sizes.extraSpace - (sizes.maxColumns - sizes.columnsPerPage) * sizes.itemWidth;
                                width = (sizes.itemOffsetWidth + (spaceToDistribute / sizes.maxColumns)) + "px";
                            }

                            var extraMarginRight;
                            var extraMarginLeft;

                            if (sizes.rtl) {
                                extraMarginRight = (isFirstColumnOnPage ? sizes.leadingEdge : 0);
                                extraMarginLeft = (isLastColumnOnPage ? extraTrailingMargin : 0);
                            } else {
                                extraMarginRight = (isLastColumnOnPage ? extraTrailingMargin : 0);
                                extraMarginLeft = (isFirstColumnOnPage ? sizes.leadingEdge : 0);
                            }

                            marginRight = extraMarginRight + sizes.itemMarginRight + "px";
                            marginLeft = extraMarginLeft + sizes.itemMarginLeft + "px";
                        } else {
                            marginRight = "";
                            marginLeft = "";
                        }

                        if (itemEl.style.marginRight !== marginRight) {
                            itemEl.style.marginRight = marginRight;
                        }
                        if (itemEl.style.marginLeft !== marginLeft) {
                            itemEl.style.marginLeft = marginLeft;
                        }
                        if (itemEl.style.width !== width) {
                            itemEl.style.width = width;
                        }
                    }
                },

                _updatePageUI: function NavBarContainer_updatePageUI() {
                    this._measure();
                    var currentPage = this._currentPage;

                    this._hasPreviousContent = (currentPage !== 0);
                    this._hasNextContent = (currentPage < this._sizes.pages - 1);
                    this._updateArrows();

                    // Always output the pagination indicators so they reserves up space.
                    if (this._indicatorCount !== this._sizes.pages) {
                        this._indicatorCount = this._sizes.pages;
                        this._pageindicatorsEl.innerHTML = new Array(this._sizes.pages + 1).join('<span class="' + NavBarContainer._ClassName.indicator + '"></span>');
                    }

                    for (var i = 0; i < this._pageindicatorsEl.children.length; i++) {
                        if (i === currentPage) {
                            _ElementUtilities.addClass(this._pageindicatorsEl.children[i], NavBarContainer._ClassName.currentindicator);
                        } else {
                            _ElementUtilities.removeClass(this._pageindicatorsEl.children[i], NavBarContainer._ClassName.currentindicator);
                        }
                    }

                    if (this._sizes.pages > 1) {
                        this._viewportEl.style.overflowX = this._panningDisabled ? "hidden" : "";
                        this._pageindicatorsEl.style.visibility = "";
                    } else {
                        this._viewportEl.style.overflowX = "hidden";
                        this._pageindicatorsEl.style.visibility = "hidden";
                    }

                    if (this._sizes.pages <= 1 || this._layout !== _UI.Orientation.horizontal) {
                        this._ariaStartMarker.removeAttribute("aria-flowto");
                        this._ariaEndMarker.removeAttribute("x-ms-aria-flowfrom");
                    } else {
                        var firstIndexOnCurrentPage = currentPage * this._sizes.rowsPerPage * this._sizes.columnsPerPage;
                        var firstItem = this._surfaceEl.children[firstIndexOnCurrentPage].winControl._buttonEl;
                        _ElementUtilities._ensureId(firstItem);
                        this._ariaStartMarker.setAttribute("aria-flowto", firstItem.id);

                        var lastIndexOnCurrentPage = Math.min(this._surfaceEl.children.length - 1, (currentPage + 1) * this._sizes.rowsPerPage * this._sizes.columnsPerPage - 1);
                        var lastItem = this._surfaceEl.children[lastIndexOnCurrentPage].winControl._buttonEl;
                        _ElementUtilities._ensureId(lastItem);
                        this._ariaEndMarker.setAttribute("x-ms-aria-flowfrom", lastItem.id);
                    }
                },

                _closeSplitIfOpen: function NavBarContainer_closeSplitIfOpen() {
                    if (this._currentSplitNavItem) {
                        if (this._currentSplitNavItem.splitOpened) {
                            this._currentSplitNavItem._toggleSplit();
                        }
                        this._currentSplitNavItem = null;
                    }
                },

                _updateArrows: function NavBarContainer_updateArrows() {
                    var hasLeftContent = this._sizes.rtl ? this._hasNextContent : this._hasPreviousContent;
                    var hasRightContent = this._sizes.rtl ? this._hasPreviousContent : this._hasNextContent;

                    var that = this;
                    // Previous and next are the arrows, not states. On mouse hover the arrows fade in immediately. If you
                    // mouse out the arrows fade out after a delay. When you reach the last/first page, the corresponding
                    // arrow fades out immediately as well.
                    if ((this._mouseInViewport || this._panningDisabled) && hasLeftContent) {
                        this._leftArrowWaitingToFadeOut && this._leftArrowWaitingToFadeOut.cancel();
                        this._leftArrowWaitingToFadeOut = null;
                        this._leftArrowFadeOut && this._leftArrowFadeOut.cancel();
                        this._leftArrowFadeOut = null;
                        this._leftArrowEl.style.visibility = '';
                        this._leftArrowFadeIn = this._leftArrowFadeIn || Animations.fadeIn(this._leftArrowEl);
                    } else {
                        if (hasLeftContent) {
                            // If we need a delayed fade out and we are already running a delayed fade out just use that one, don't extend it.
                            // Otherwise create a delayed fade out.
                            this._leftArrowWaitingToFadeOut = this._leftArrowWaitingToFadeOut || Promise.timeout(_TransitionAnimation._animationTimeAdjustment(buttonFadeDelay));
                        } else {
                            // If we need a immediate fade out and already have a delayed fade out cancel that one and create an immediate one.
                            this._leftArrowWaitingToFadeOut && this._leftArrowWaitingToFadeOut.cancel();
                            this._leftArrowWaitingToFadeOut = Promise.wrap();
                        }
                        this._leftArrowWaitingToFadeOut.then(function () {
                            // After the delay cancel any fade in if running. If we already were fading out continue it otherwise start the fade out.
                            this._leftArrowFadeIn && this._leftArrowFadeIn.cancel();
                            this._leftArrowFadeIn = null;
                            this._leftArrowFadeOut = this._leftArrowFadeOut || Animations.fadeOut(this._leftArrowEl).then(function () {
                                that._leftArrowEl.style.visibility = 'hidden';
                            });
                        }.bind(this));
                    }

                    // Same pattern for Next arrow.
                    if ((this._mouseInViewport || this._panningDisabled) && hasRightContent) {
                        this._rightArrowWaitingToFadeOut && this._rightArrowWaitingToFadeOut.cancel();
                        this._rightArrowWaitingToFadeOut = null;
                        this._rightArrowFadeOut && this._rightArrowFadeOut.cancel();
                        this._rightArrowFadeOut = null;
                        this._rightArrowEl.style.visibility = '';
                        this._rightArrowFadeIn = this._rightArrowFadeIn || Animations.fadeIn(this._rightArrowEl);
                    } else {
                        if (hasRightContent) {
                            this._rightArrowWaitingToFadeOut = this._rightArrowWaitingToFadeOut || Promise.timeout(_TransitionAnimation._animationTimeAdjustment(buttonFadeDelay));
                        } else {
                            this._rightArrowWaitingToFadeOut && this._rightArrowWaitingToFadeOut.cancel();
                            this._rightArrowWaitingToFadeOut = Promise.wrap();
                        }
                        this._rightArrowWaitingToFadeOut.then(function () {
                            this._rightArrowFadeIn && this._rightArrowFadeIn.cancel();
                            this._rightArrowFadeIn = null;
                            this._rightArrowFadeOut = this._rightArrowFadeOut || Animations.fadeOut(this._rightArrowEl).then(function () {
                                that._rightArrowEl.style.visibility = 'hidden';
                            });
                        }.bind(this));
                    }
                },

                _navbarCommandInvokedHandler: function NavBarContainer_navbarCommandInvokedHandler(ev) {
                    var srcElement = ev.target;
                    var index = -1;
                    while (srcElement) {
                        index++;
                        srcElement = srcElement.previousSibling;
                    }

                    this._fireEvent(NavBarContainer._EventName.invoked, {
                        index: index,
                        navbarCommand: ev.target.winControl,
                        data: this._repeater ? this._repeater.data.getAt(index) : null
                    });
                },

                _navbarCommandSplitToggleHandler: function NavBarContainer_navbarCommandSplitToggleHandler(ev) {
                    var srcElement = ev.target;
                    var index = -1;
                    while (srcElement) {
                        index++;
                        srcElement = srcElement.previousSibling;
                    }

                    var navbarCommand = ev.target.winControl;

                    this._closeSplitIfOpen();

                    if (navbarCommand.splitOpened) {
                        this._currentSplitNavItem = navbarCommand;
                    }

                    this._fireEvent(NavBarContainer._EventName.splitToggle, {
                        opened: navbarCommand.splitOpened,
                        index: index,
                        navbarCommand: navbarCommand,
                        data: this._repeater ? this._repeater.data.getAt(index) : null
                    });
                },

                _fireEvent: function NavBarContainer_fireEvent(type, detail) {
                    var event = _Global.document.createEvent("CustomEvent");
                    event.initCustomEvent(type, true, false, detail);
                    this.element.dispatchEvent(event);
                },

                _writeProfilerMark: function NavBarContainer_writeProfilerMark(text) {
                    var message = "WinJS.UI.NavBarContainer:" + this._id + ":" + text;
                    _WriteProfilerMark(message);
                    _Log.log && _Log.log(message, null, "navbarcontainerprofiler");
                },

                dispose: function NavBarContainer_dispose() {
                    /// <signature helpKeyword="WinJS.UI.NavBarContainer.dispose">
                    /// <summary locid="WinJS.UI.NavBarContainer.dispose">
                    /// Disposes this control.
                    /// </summary>
                    /// <compatibleWith platform="Windows" minVersion="8.1"/>
                    /// </signature>
                    if (this._disposed) {
                        return;
                    }
                    this._disposed = true;

                    if (this._appBarEl) {
                        this._appBarEl.removeEventListener('beforeshow', this._closeSplitAndResetBound);
                        this._appBarEl.removeEventListener('beforeshow', this._resizeImplBound);
                    }

                    Navigation.removeEventListener('navigated', this._closeSplitAndResetBound);

                    this._leftArrowWaitingToFadeOut && this._leftArrowWaitingToFadeOut.cancel();
                    this._leftArrowFadeOut && this._leftArrowFadeOut.cancel();
                    this._leftArrowFadeIn && this._leftArrowFadeIn.cancel();
                    this._rightArrowWaitingToFadeOut && this._rightArrowWaitingToFadeOut.cancel();
                    this._rightArrowFadeOut && this._rightArrowFadeOut.cancel();
                    this._rightArrowFadeIn && this._rightArrowFadeIn.cancel();

                    _ElementUtilities._resizeNotifier.unsubscribe(this._element, this._boundResizeHandler);

                    this._removeDataChangingEvents();
                    this._removeDataChangedEvents();
                }
            }, {
                // Names of classes used by the NavBarContainer.
                _ClassName: {
                    navbarcontainer: "win-navbarcontainer",
                    pageindicators: "win-navbarcontainer-pageindicator-box",
                    indicator: "win-navbarcontainer-pageindicator",
                    currentindicator: "win-navbarcontainer-pageindicator-current",
                    vertical: "win-navbarcontainer-vertical",
                    horizontal: "win-navbarcontainer-horizontal",
                    viewport: "win-navbarcontainer-viewport",
                    surface: "win-navbarcontainer-surface",
                    navarrow: "win-navbarcontainer-navarrow",
                    navleftarrow: "win-navbarcontainer-navleft",
                    navrightarrow: "win-navbarcontainer-navright"
                },
                _EventName: {
                    invoked: eventNames.invoked,
                    splitToggle: eventNames.splittoggle
                }
            });
            _Base.Class.mix(NavBarContainer, _Control.DOMEventMixin);
            return NavBarContainer;
        })
    });

});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('WinJS/Controls/NavBar',[
    '../Core/_Global',
    '../Core/_WinRT',
    '../Core/_Base',
    '../Core/_BaseUtils',
    '../Core/_Events',
    '../Core/_WriteProfilerMark',
    '../Promise',
    '../Scheduler',
    '../Utilities/_ElementUtilities',
    '../Utilities/_Hoverable',
    './AppBar',
    './NavBar/_Command',
    './NavBar/_Container',
    'require-style!less/desktop/controls',
    'require-style!less/phone/controls'
], function NavBarInit(_Global,_WinRT, _Base, _BaseUtils, _Events, _WriteProfilerMark, Promise, Scheduler, _ElementUtilities, _Hoverable, AppBar, _Command, _Container) {
    "use strict";

    var customLayout = "custom";

    _Base.Namespace.define("WinJS.UI", {
        /// <field>
        /// <summary locid="WinJS.UI.NavBar">
        /// Displays navigation commands in a toolbar that the user can show or hide.
        /// </summary>
        /// <compatibleWith platform="Windows" minVersion="8.1"/>
        /// </field>
        /// <icon src="ui_winjs.ui.navbar.12x12.png" width="12" height="12" />
        /// <icon src="ui_winjs.ui.navbar.16x16.png" width="16" height="16" />
        /// <htmlSnippet supportsContent="true"><![CDATA[<div data-win-control="WinJS.UI.NavBar">
        /// <div data-win-control="WinJS.UI.NavBarContainer">
        /// <div data-win-control="WinJS.UI.NavBarCommand" data-win-options="{location:'/pages/home/home.html',label:'Home',icon:WinJS.UI.AppBarIcon.home}"></div>
        /// </div>
        /// </div>]]></htmlSnippet>
        /// <event name="beforeshow" locid="WinJS.UI.NavBar_e:beforeshow">Raised just before showing the NavBar.</event>
        /// <event name="aftershow" locid="WinJS.UI.NavBar_e:aftershow">Raised immediately after an NavBar is fully shown.</event>
        /// <event name="beforehide" locid="WinJS.UI.NavBar_e:beforehide">Raised just before hiding the  NavBar.</event>
        /// <event name="afterhide" locid="WinJS.UI.NavBar_e:afterhide">Raised immediately after the NavBar is fully hidden.</event>
        /// <event name="childrenprocessed" locid="WinJS.UI.NavBar_e:childrenprocessed">Fired when children of NavBar control have been processed from a WinJS.UI.processAll call.</event>
        /// <part name="navbar" class="win-navbar" locid="WinJS.UI.NavBar_part:navbar">Styles the entire NavBar.</part>
        /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
        /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
        /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
        NavBar: _Base.Namespace._lazy(function () {
            var childrenProcessedEventName = "childrenprocessed";
            var createEvent = _Events._createEventProperty;

            var NavBar = _Base.Class.derive(AppBar.AppBar, function NavBar_ctor(element, options) {
                /// <signature helpKeyword="WinJS.UI.NavBar.NavBar">
                /// <summary locid="WinJS.UI.NavBar.constructor">
                /// Creates a new NavBar.
                /// </summary>
                /// <param name="element" type="HTMLElement" domElement="true" locid="WinJS.UI.NavBar.constructor_p:element">
                /// The DOM element that will host the new NavBar control.
                /// </param>
                /// <param name="options" type="Object" locid="WinJS.UI.NavBar.constructor_p:options">
                /// An object that contains one or more property/value pairs to apply to the new control. Each property of the options object corresponds to one of the control's
                /// properties or events.
                /// </param>
                /// <returns type="WinJS.UI.NavBar" locid="WinJS.UI.NavBar.constructor_returnValue">
                /// The new NavBar control.
                /// </returns>
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </signature>

                options = options || {};

                // Shallow copy object so we can modify it.
                options = _BaseUtils._shallowCopy(options);

                // Default to Placement = Top and Layout = Custom
                options.placement = options.placement || "top";
                options.layout = customLayout;

                AppBar.AppBar.call(this, element, options);

                this._element.addEventListener("beforeshow", this._handleBeforeShow.bind(this));

                _ElementUtilities.addClass(this.element, NavBar._ClassName.navbar);

                if (_WinRT.Windows.ApplicationModel.DesignMode.designModeEnabled) {
                    this._processChildren();
                } else {
                    Scheduler.schedule(this._processChildren.bind(this), Scheduler.Priority.idle, null, "WinJS.UI.NavBar.processChildren");
                }
            }, {
                // Block others from setting the layout property.

                /// <field type="String" defaultValue="commands" oamOptionsDatatype="WinJS.UI.NavBar.layout" locid="WinJS.UI.NavBar.layout" helpKeyword="WinJS.UI.NavBar.layout">
                /// The layout of the NavBar contents.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                layout: {
                    get: function () {
                        return customLayout;
                    },
                    set: function () {
                        Object.getOwnPropertyDescriptor(AppBar.AppBar.prototype, "layout").set.call(this, customLayout);
                    },
                },

                /// <field type="Function" locid="WinJS.UI.NavBar.onchildrenprocessed" helpKeyword="WinJS.UI.NavBar.onchildrenprocessed">
                /// Raised when children of NavBar control have been processed by a WinJS.UI.processAll call.
                /// <compatibleWith platform="Windows" minVersion="8.1"/>
                /// </field>
                onchildrenprocessed: createEvent(childrenProcessedEventName),

                _processChildren: function NavBar_processChildren() {
                    // The NavBar control schedules processAll on its children at idle priority to avoid hurting startup
                    // performance. If the NavBar is shown before the scheduler gets to the idle job, the NavBar will
                    // immediately call processAll on its children. If your app needs the children to be processed before
                    // the scheduled job executes, you may call processChildren to force the processAll call.
                    if (!this._processed) {
                        this._processed = true;

                        this._writeProfilerMark("processChildren,StartTM");
                        var that = this;
                        var processed = Promise.as();
                        if (this._processors) {
                            this._processors.forEach(function (processAll) {
                                for (var i = 0, len = that.element.children.length; i < len; i++) {
                                    (function (child) {
                                        processed = processed.then(function () {
                                            processAll(child);
                                        });
                                    }(that.element.children[i]));
                                }
                            });
                        }
                        return processed.then(
                            function () {
                                that._writeProfilerMark("processChildren,StopTM");
                                that._fireEvent(NavBar._EventName.childrenProcessed);
                            },
                            function () {
                                that._writeProfilerMark("processChildren,StopTM");
                                that._fireEvent(NavBar._EventName.childrenProcessed);
                            }
                        );
                    }
                    return Promise.wrap();
                },

                _show: function NavBar_show() {
                    // Override _show to call processChildren first.
                    //
                    if (this.disabled) {
                        return;
                    }
                    var that = this;
                    this._processChildren().then(function () {
                        AppBar.AppBar.prototype._show.call(that);
                    });
                },

                _handleBeforeShow: function NavBar_handleBeforeShow() {
                    // Navbar needs to ensure its elements to have their correct height and width after AppBar changes display="none"
                    // to  display="" and AppBar needs the elements to have their final height before it measures its own element height
                    // to do the slide in animation over the correct amount of pixels.
                    if (this._disposed) {
                        return;
                    }

                    var navbarcontainerEls = this.element.querySelectorAll('.win-navbarcontainer');
                    for (var i = 0; i < navbarcontainerEls.length; i++) {
                        navbarcontainerEls[i].winControl.forceLayout();
                    }
                },

                _fireEvent: function NavBar_fireEvent(type, detail) {
                    var event = _Global.document.createEvent("CustomEvent");
                    event.initCustomEvent(type, true, false, detail || {});
                    this.element.dispatchEvent(event);
                },

                _writeProfilerMark: function NavBar_writeProfilerMark(text) {
                    _WriteProfilerMark("WinJS.UI.NavBar:" + this._id + ":" + text);
                }
            }, {
                _ClassName: {
                    navbar: "win-navbar"
                },
                _EventName: {
                    childrenProcessed: childrenProcessedEventName
                },
                isDeclarativeControlContainer: _BaseUtils.markSupportedForProcessing(function (navbar, callback) {
                    if (navbar._processed) {
                        for (var i = 0, len = navbar.element.children.length; i < len; i++) {
                            callback(navbar.element.children[i]);
                        }
                    } else {
                        navbar._processors = navbar._processors || [];
                        navbar._processors.push(callback);
                    }
                })
            });

            return NavBar;
        })
    });

});
// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// ViewBox control
define('WinJS/Controls/ViewBox',[
    '../Core/_Global',
    '../Core/_Base',
    '../Core/_BaseUtils',
    '../Core/_ErrorFromName',
    '../Core/_Resources',
    '../Scheduler',
    '../Utilities/_Control',
    '../Utilities/_Dispose',
    '../Utilities/_ElementUtilities',
    '../Utilities/_Hoverable',
    'require-style!less/desktop/controls',
    'require-style!less/phone/controls'
    ], function viewboxInit(_Global, _Base, _BaseUtils, _ErrorFromName, _Resources, Scheduler, _Control, _Dispose, _ElementUtilities, _Hoverable) {
    "use strict";

    _Base.Namespace.define("WinJS.UI", {
        /// <field>
        /// <summary locid="WinJS.UI.ViewBox">
        /// Scales a single child element to fill the available space without
        /// resizing it. This control reacts to changes in the size of the container as well as
        /// changes in size of the child element. For example, a media query may result in
        /// a change in aspect ratio.
        /// </summary>
        /// </field>
        /// <name locid="WinJS.UI.ViewBox_name">View Box</name>
        /// <icon src="ui_winjs.ui.viewbox.12x12.png" width="12" height="12" />
        /// <icon src="ui_winjs.ui.viewbox.16x16.png" width="16" height="16" />
        /// <htmlSnippet supportsContent="true"><![CDATA[<div data-win-control="WinJS.UI.ViewBox"><div>ViewBox</div></div>]]></htmlSnippet>
        /// <resource type="javascript" src="//WinJS.3.0/js/base.js" shared="true" />
        /// <resource type="javascript" src="//WinJS.3.0/js/ui.js" shared="true" />
        /// <resource type="css" src="//WinJS.3.0/css/ui-dark.css" shared="true" />
        ViewBox: _Base.Namespace._lazy(function () {

            var strings = {
                get invalidViewBoxChildren() { return "ViewBox expects to only have one child element"; },
            };

            function onresize(control) {
                if (control && !control._resizing) {
                    control._resizing = control._resizing || 0;
                    control._resizing++;
                    try {
                        control._updateLayout();
                    } finally {
                        control._resizing--;
                    }
                }
            }

            function onresizeBox(ev) {
                if (ev.target) {
                    onresize(ev.target.winControl);
                }
            }

            function onresizeSizer(ev) {
                if (ev.target) {
                    onresize(ev.target.parentElement.winControl);
                }
            }

            var ViewBox = _Base.Class.define(function ViewBox_ctor(element) {
                /// <signature helpKeyword="WinJS.UI.ViewBox.ViewBox">
                /// <summary locid="WinJS.UI.ViewBox.constructor">Initializes a new instance of the ViewBox control</summary>
                /// <param name="element" type="HTMLElement" domElement="true" mayBeNull="true" locid="WinJS.UI.ViewBox.constructor_p:element">
                /// The DOM element that functions as the scaling box. This element fills 100% of the width and height allotted to it.
                /// </param>
                /// <param name="options" type="Object" optional="true" locid="WinJS.UI.ViewBox.constructor_p:options">
                /// The set of options to be applied initially to the ViewBox control.
                /// </param>
                /// <returns type="WinJS.UI.ViewBox" locid="WinJS.UI.ViewBox.constructor_returnValue">A constructed ViewBox control.</returns>
                /// </signature>
                this._disposed = false;

                this._element = element || _Global.document.createElement("div");
                var box = this.element;
                box.winControl = this;
                _ElementUtilities.addClass(box, "win-disposable");
                _ElementUtilities.addClass(box, "win-viewbox");
                this.forceLayout();
            }, {
                _sizer: null,
                _element: null,

                /// <field type="HTMLElement" domElement="true" hidden="true" locid="WinJS.UI.ViewBox.element" helpKeyword="WinJS.UI.ViewBox.element">
                /// Gets the DOM element that functions as the scaling box.
                /// </field>
                element: {
                    get: function () { return this._element; }
                },

                _rtl: {
                    get: function () {
                        return _Global.getComputedStyle(this.element).direction === "rtl";
                    }
                },

                _initialize: function () {
                    var box = this.element;
                    if (box.firstElementChild !== this._sizer) {
                        if (_BaseUtils.validation) {
                            if (box.childElementCount !== 1) {
                                throw new _ErrorFromName("WinJS.UI.ViewBox.InvalidChildren", strings.invalidViewBoxChildren);
                            }
                        }
                        if (this._sizer) {
                            this._sizer.onresize = null;
                        }
                        var sizer = box.firstElementChild;
                        this._sizer = sizer;
                        if (sizer) {
                            _ElementUtilities._resizeNotifier.subscribe(box, onresizeBox);
                            box.addEventListener("mselementresize", onresizeBox);
                            _ElementUtilities._resizeNotifier.subscribe(sizer, onresizeSizer);
                            sizer.addEventListener("mselementresize", onresizeSizer);
                        }
                        if (box.clientWidth === 0 && box.clientHeight === 0) {
                            var that = this;
                            // Wait for the viewbox to get added to the DOM. It should be added
                            // in the synchronous block in which _initialize was called.
                            Scheduler.schedule(function ViewBox_async_initialize() {
                                that._updateLayout();
                            }, Scheduler.Priority.normal, null, "WinJS.UI.ViewBox._updateLayout");
                        }
                    }
                },
                _updateLayout: function () {
                    var sizer = this._sizer;
                    if (sizer) {
                        var box = this.element;
                        var w = sizer.clientWidth;
                        var h = sizer.clientHeight;
                        var bw = box.clientWidth;
                        var bh = box.clientHeight;
                        var wRatio = bw / w;
                        var hRatio = bh / h;
                        var mRatio = Math.min(wRatio, hRatio);
                        var transX = Math.abs(bw - (w * mRatio)) / 2;
                        var transY = Math.abs(bh - (h * mRatio)) / 2;
                        var rtl = this._rtl;
                        this._sizer.style[_BaseUtils._browserStyleEquivalents["transform"].scriptName] = "translate(" + (rtl ? "-" : "") + transX + "px," + transY + "px) scale(" + mRatio + ")";
                        this._sizer.style[_BaseUtils._browserStyleEquivalents["transform-origin"].scriptName] = rtl ? "top right" : "top left";
                    }
                },

                dispose: function () {
                    /// <signature helpKeyword="WinJS.UI.ViewBox.dispose">
                    /// <summary locid="WinJS.UI.ViewBox.dispose">
                    /// Disposes this ViewBox.
                    /// </summary>
                    /// </signature>
                    if (this._disposed) {
                        return;
                    }

                    if (this.element) {
                        _ElementUtilities._resizeNotifier.unsubscribe(this.element, onresizeBox);
                    }
                    if (this._sizer) {
                        _ElementUtilities._resizeNotifier.unsubscribe(this._sizer, onresizeSizer);
                    }

                    this._disposed = true;
                    _Dispose.disposeSubTree(this._element);
                },

                forceLayout: function () {
                    this._initialize();
                    this._updateLayout();
                }
            });
            _Base.Class.mix(ViewBox, _Control.DOMEventMixin);
            return ViewBox;
        })
    });

});

// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
define('ui',[
    'WinJS/Core/_WinJS',
    'WinJS/VirtualizedDataSource',
    'WinJS/Controls/IntrinsicControls',
    'WinJS/Controls/ListView',
    'WinJS/Controls/FlipView',
    'WinJS/Controls/ItemContainer',
    'WinJS/Controls/Repeater',
    'WinJS/Controls/DatePicker',
    'WinJS/Controls/TimePicker',
    'WinJS/Controls/BackButton',
    'WinJS/Controls/Rating',
    'WinJS/Controls/ToggleSwitch',
    'WinJS/Controls/SemanticZoom',
    'WinJS/Controls/Pivot',
    'WinJS/Controls/Hub',
    'WinJS/Controls/Flyout',
    'WinJS/Controls/AppBar',
    'WinJS/Controls/Menu',
    'WinJS/Controls/SearchBox',
    'WinJS/Controls/SettingsFlyout',
    'WinJS/Controls/NavBar',
    'WinJS/Controls/Tooltip',
    'WinJS/Controls/ViewBox'
    ], function (_WinJS) {
    "use strict";

    //Wrapper module

    return _WinJS;
});

        require(['WinJS/Core/_WinJS', 'ui'], function (_WinJS) {
            global.WinJS = _WinJS;
            return _WinJS;
        });
    }));
}(this));

