// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License.txt in the project root for license information. // Proxy Data Source module DatasourceTestComponents { "use strict"; var UI = WinJS.UI; var Promise = WinJS.Promise; // Private statics var errorDoesNotExist = function errorDoesNotExist () { return Promise.wrapError(new WinJS.ErrorFromName(UI.FetchError.doesNotExist.toString())); }; var errorNoLongerMeaningful = function errorNoLongerMeaningful () { return Promise.wrapError(new WinJS.ErrorFromName(UI.EditError.noLongerMeaningful.toString())); }; var ProxyDataAdapter = WinJS.Class.define(function (array, keyOf) { // Constructor this._array = array; this._keyOf = keyOf; // Map from keys to indices this._keyCache = {}; this._cacheSize = 0; }, { // Public members // setNotificationHandler: not implemented // compareByIdentity: not set // itemSignature: not implemented // itemsFromStart: not implemented itemsFromEnd: function (count) { var len = this._array.length; return ( len === 0 ? errorDoesNotExist() : this.itemsFromIndex(len - 1, Math.min(count - 1, len - 1), 0) ); }, itemsFromKey: function (key, countBefore, countAfter) { var index = this._indexFromKey(key); return ( +index === index ? this.itemsFromIndex(index, Math.min(countBefore, index), countAfter) : errorDoesNotExist() ); }, itemsFromIndex: function (index, countBefore, countAfter) { var len = this._array.length; if (index >= len) { return errorDoesNotExist(); } else { var first = index - countBefore, last = Math.min(index + countAfter, len - 1), items = new Array(last - first + 1); for (var i = first; i <= last; i++) { items[i - first] = this._item(i); } return Promise.wrap({ items: items, offset: countBefore, absoluteIndex: index, // Return index because itemsFromKey shares this implementation totalCount: len }); } }, // itemsFromDescription: not implemented getCount: function () { return Promise.wrap(this._array.length); }, // beginEdits: not implemented insertAtStart: function (key, data) { // key parameter is ignored, as keys are determined from data return this._insert(0, data); }, insertBefore: function (key, data, nextKey, nextIndexHint) { // key parameter is ignored, as keys are determined from data return this._insert(this._indexFromKey(nextKey, nextIndexHint), data); }, insertAfter: function (key, data, previousKey, previousIndexHint) { // key parameter is ignored, as keys are determined from data return this._insert(this._indexFromKey(previousKey, previousIndexHint) + 1, data); }, insertAtEnd: function (key, data) { // key parameter is ignored, as keys are determined from data return this._insert(this._array.length, data); }, change: function (key, newData, indexHint) { var index = this._indexFromKey(key, indexHint); if (+index !== index) { return errorNoLongerMeaningful(); } this._array[index] = newData; return Promise.wrap(); }, moveToStart: function (key, indexHint) { return this._move(key, this._indexFromKey(key, indexHint), 0); }, moveBefore: function (key, nextKey, indexHint, nextIndexHint) { return this._move(key, this._indexFromKey(key, indexHint), this._indexFromKey(nextKey, nextIndexHint)); }, moveAfter: function (key, previousKey, indexHint, previousIndexHint) { return this._move(key, this._indexFromKey(key, indexHint), this._indexFromKey(previousKey, previousIndexHint) + 1); }, moveToEnd: function (key, indexHint) { return this._move(key, this._indexFromKey(key, indexHint), this._array.length); }, remove: function (key, indexHint) { var index = this._indexFromKey(key, indexHint); if (+index !== index) { return errorNoLongerMeaningful(); } this._array.splice(index, 1); this._removeKeyFromCache(key); this._adjustCachedIndices(index, this._array.length, -1); return Promise.wrap(); }, // endEdits: not implemented // Private members _keyAtIndex: function (index) { var key = this._keyOf(this._array[index]); // Opportunistically cache every key var cachedIndex = this._keyCache[key]; if (+cachedIndex === cachedIndex) { if (cachedIndex !== index) { this._keyCache[key] = index; } } else { // See if the cache has grown too large if (this._cacheSize > 1.1 * this._array.length) { this._keyCache = {}; this._cacheSize = 1; } else { this._cacheSize++; } this._keyCache[key] = index; } return key; }, _adjustCachedIndices: function (indexFirst, indexMax, delta) { for (var key in this._keyCache) { var index = this._keyCache[key]; if (index >= indexFirst && index < indexMax) { this._keyCache[key] = index + delta; } } }, _removeKeyFromCache: function (key) { var indexCached = this._keyCache[key]; if (+indexCached === indexCached) { delete this._keyCache[key]; this._cacheSize--; } }, _indexFromKey: function (key, indexHint) { var len = this._array.length; var indexCached = this._keyCache[key]; if (indexCached < len) { if (this._keyAtIndex(indexCached) === key) { return indexCached; } // Start looking from the cached index indexHint = indexCached; } if (+indexHint !== indexHint) { indexHint = 0; } for (var delta = 1, deltaMax = Math.max(indexHint, len - 1 - indexHint) ; delta <= deltaMax; delta++) { var indexSearch; if (delta <= indexHint) { indexSearch = indexHint - delta; if (this._keyAtIndex(indexSearch) === key) { return indexSearch; } } indexSearch = indexHint + delta; if (indexSearch < len && this._keyAtIndex(indexSearch) === key) { return indexSearch; } } // The key no longer exists this._removeKeyFromCache(key); return null; }, _item: function (index) { return { key: this._keyAtIndex(index), data: this._array[index] }; }, _insert: function (index, data) { if (+index !== index) { return errorNoLongerMeaningful(); } this._array.splice(index, 0, data); this._adjustCachedIndices(index + 1, this._array.length, 1); return Promise.wrap(this._item(index)); }, _move: function (key, indexFrom, indexTo) { if (+indexFrom !== indexFrom || +indexTo !== indexTo) { return errorNoLongerMeaningful(); } var data = this._array[indexFrom]; this._array.splice(indexFrom, 1); if (indexFrom < indexTo) { indexTo--; } this._array.splice(indexTo, 0, data); if (indexFrom < indexTo) { this._adjustCachedIndices(indexFrom, indexTo, -1); } else if (indexFrom > indexTo) { this._adjustCachedIndices(indexTo + 1, indexFrom + 1, 1); } var indexCached = this._keyCache[key]; if (+indexCached !== indexCached) { this._cacheSize++; } this._keyCache[key] = indexTo; return Promise.wrap(); } }); // Public definition /// /// A data source that wraps a given array. The array may be manipulated directly, in which case plausible change /// notifications will be generated when an inconsistency is detected or the data source is invalidated. /// /// /// The array to wrap. /// /// /// A callback that returns a unique and invariant string to serve as the key for the array element passed to it. /// export var ProxyDataSource = WinJS.Class.derive(UI.VirtualizedDataSource, function (array, keyOf) { this._baseDataSourceConstructor(new ProxyDataAdapter(array, keyOf)); }); }