winjs
Version:
WinJS is a set of JavaScript toolkits that allow developers to build applications using HTML/JS/CSS technology.
1,075 lines (899 loc) • 3.05 MB
JavaScript
/*! 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.4 4.4.2.winjs.2017.3.14 ui.js,StartTM');
if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
// CommonJS
factory(require("./base"));
} else {
// No module system
factory(globalObject.WinJS);
}
globalObject.msWriteProfilerMark && msWriteProfilerMark('WinJS.4.4 4.4.2.winjs.2017.3.14 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