You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ti...@apache.org on 2015/06/26 19:01:28 UTC
[06/16] cordova-windows git commit: CB-9252: Migrate WinJS to an NPM
dependency
http://git-wip-us.apache.org/repos/asf/cordova-windows/blob/99107c62/node_modules/winjs/js/ui.js
----------------------------------------------------------------------
diff --git a/node_modules/winjs/js/ui.js b/node_modules/winjs/js/ui.js
new file mode 100644
index 0000000..367d5fa
--- /dev/null
+++ b/node_modules/winjs/js/ui.js
@@ -0,0 +1,54913 @@
+
+/*! Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License.txt in the project root for license information. */
+(function () {
+
+ var globalObject =
+ typeof window !== 'undefined' ? window :
+ typeof self !== 'undefined' ? self :
+ typeof global !== 'undefined' ? global :
+ {};
+ (function (factory) {
+ if (typeof define === 'function' && define.amd) {
+ // amd
+ define(["./base"], factory);
+ } else {
+ globalObject.msWriteProfilerMark && msWriteProfilerMark('WinJS.4.0 4.0.1.winjs.2015.6.10 ui.js,StartTM');
+ if (typeof module !== 'undefined') {
+ // CommonJS
+ factory(require("./base"));
+ } else {
+ // No module system
+ factory(globalObject.WinJS);
+ }
+ globalObject.msWriteProfilerMark && msWriteProfilerMark('WinJS.4.0 4.0.1.winjs.2015.6.10 ui.js,StopTM');
+ }
+ }(function (WinJS) {
+
+
+var require = WinJS.Utilities._require;
+var define = WinJS.Utilities._define;
+
+// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. 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);
+
<TRUNCATED>
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org