UNPKG

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
/*! 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