diff --git a/bower.json b/bower.json index 80274b8..f5e9c15 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "aurelia-ui-virtualization", - "version": "0.3.2", + "version": "0.4.0", "description": "A plugin that provides a virtualized repeater and other virtualization services.", "keywords": [ "aurelia", diff --git a/dist/amd/array-virtual-repeat-strategy.js b/dist/amd/array-virtual-repeat-strategy.js index d2062e9..df37790 100644 --- a/dist/amd/array-virtual-repeat-strategy.js +++ b/dist/amd/array-virtual-repeat-strategy.js @@ -1,26 +1,53 @@ -define(['exports', 'aurelia-templating-resources/array-repeat-strategy', 'aurelia-templating-resources/repeat-utilities'], function (exports, _aureliaTemplatingResourcesArrayRepeatStrategy, _aureliaTemplatingResourcesRepeatUtilities) { +define(['exports', 'aurelia-templating-resources/array-repeat-strategy', 'aurelia-templating-resources/repeat-utilities', './utilities'], function (exports, _arrayRepeatStrategy, _repeatUtilities, _utilities) { 'use strict'; - exports.__esModule = true; + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.ArrayVirtualRepeatStrategy = undefined; - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + function _possibleConstructorReturn(self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } - function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + return call && (typeof call === "object" || typeof call === "function") ? call : self; + } + + function _inherits(subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); + } - var ArrayVirtualRepeatStrategy = (function (_ArrayRepeatStrategy) { + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; + } + + var ArrayVirtualRepeatStrategy = exports.ArrayVirtualRepeatStrategy = function (_ArrayRepeatStrategy) { _inherits(ArrayVirtualRepeatStrategy, _ArrayRepeatStrategy); function ArrayVirtualRepeatStrategy() { _classCallCheck(this, ArrayVirtualRepeatStrategy); - _ArrayRepeatStrategy.apply(this, arguments); + return _possibleConstructorReturn(this, _ArrayRepeatStrategy.apply(this, arguments)); } ArrayVirtualRepeatStrategy.prototype.createFirstItem = function createFirstItem(repeat) { - var overrideContext = _aureliaTemplatingResourcesRepeatUtilities.createFullOverrideContext(repeat, repeat.items[0], 0, 1); - var view = repeat.viewFactory.create(); - view.bind(overrideContext.bindingContext, overrideContext); - repeat.viewSlot.add(view); + var overrideContext = (0, _repeatUtilities.createFullOverrideContext)(repeat, repeat.items[0], 0, 1); + repeat.addView(overrideContext.bindingContext, overrideContext); }; ArrayVirtualRepeatStrategy.prototype.instanceChanged = function instanceChanged(repeat, items) { @@ -29,27 +56,25 @@ define(['exports', 'aurelia-templating-resources/array-repeat-strategy', 'aureli ArrayVirtualRepeatStrategy.prototype._standardProcessInstanceChanged = function _standardProcessInstanceChanged(repeat, items) { for (var i = 1, ii = repeat._viewsLength; i < ii; ++i) { - var overrideContext = _aureliaTemplatingResourcesRepeatUtilities.createFullOverrideContext(repeat, items[i], i, ii); - var view = repeat.viewFactory.create(); - view.bind(overrideContext.bindingContext, overrideContext); - repeat.viewSlot.add(view); + var overrideContext = (0, _repeatUtilities.createFullOverrideContext)(repeat, items[i], i, ii); + repeat.addView(overrideContext.bindingContext, overrideContext); } }; ArrayVirtualRepeatStrategy.prototype._inPlaceProcessItems = function _inPlaceProcessItems(repeat, items) { var itemsLength = items.length; - var viewsLength = repeat.viewSlot.children.length; + var viewsLength = repeat.viewCount(); var first = repeat._getIndexOfFirstView(); while (viewsLength > repeat._viewsLength) { viewsLength--; - repeat.viewSlot.removeAt(viewsLength, true); + repeat.removeView(viewsLength, true); } var local = repeat.local; for (var i = 0; i < viewsLength; i++) { - var view = repeat.viewSlot.children[i]; + var view = repeat.view(i); var last = i === itemsLength - 1; var middle = i !== 0 && !last; @@ -60,166 +85,193 @@ define(['exports', 'aurelia-templating-resources/array-repeat-strategy', 'aureli view.bindingContext[local] = items[i + first]; view.overrideContext.$middle = middle; view.overrideContext.$last = last; - var j = view.bindings.length; - while (j--) { - _aureliaTemplatingResourcesRepeatUtilities.updateOneTimeBinding(view.bindings[j]); - } - j = view.controllers.length; - while (j--) { - var k = view.controllers[j].boundProperties.length; - while (k--) { - var binding = view.controllers[j].boundProperties[k].binding; - _aureliaTemplatingResourcesRepeatUtilities.updateOneTimeBinding(binding); - } - } + repeat.updateBindings(view); } - for (var i = viewsLength; i < repeat._viewsLength; i++) { - var overrideContext = _aureliaTemplatingResourcesRepeatUtilities.createFullOverrideContext(repeat, items[i], i, itemsLength); - var view = repeat.viewFactory.create(); - view.bind(overrideContext.bindingContext, overrideContext); - repeat.viewSlot.add(view); + var minLength = Math.min(repeat._viewsLength, items.length); + for (var _i = viewsLength; _i < minLength; _i++) { + var overrideContext = (0, _repeatUtilities.createFullOverrideContext)(repeat, items[_i], _i, itemsLength); + repeat.addView(overrideContext.bindingContext, overrideContext); } }; ArrayVirtualRepeatStrategy.prototype.instanceMutated = function instanceMutated(repeat, array, splices) { - this._updateViews(repeat, repeat.items, splices); + this._standardProcessInstanceMutated(repeat, array, splices); }; ArrayVirtualRepeatStrategy.prototype._standardProcessInstanceMutated = function _standardProcessInstanceMutated(repeat, array, splices) { - var _this = this; + var _this2 = this; + + if (repeat.__queuedSplices) { + for (var i = 0, ii = splices.length; i < ii; ++i) { + var _splices$i = splices[i]; + var index = _splices$i.index; + var removed = _splices$i.removed; + var addedCount = _splices$i.addedCount; + + mergeSplice(repeat.__queuedSplices, index, removed, addedCount); + } + repeat.__array = array.slice(0); + return; + } + + var maybePromise = this._runSplices(repeat, array.slice(0), splices); + if (maybePromise instanceof Promise) { + (function () { + var queuedSplices = repeat.__queuedSplices = []; + + var runQueuedSplices = function runQueuedSplices() { + if (!queuedSplices.length) { + delete repeat.__queuedSplices; + delete repeat.__array; + return; + } + + var nextPromise = _this2._runSplices(repeat, repeat.__array, queuedSplices) || Promise.resolve(); + nextPromise.then(runQueuedSplices); + }; + + maybePromise.then(runQueuedSplices); + })(); + } + }; + + ArrayVirtualRepeatStrategy.prototype._runSplices = function _runSplices(repeat, array, splices) { + var _this3 = this; var removeDelta = 0; - var viewSlot = repeat.viewSlot; var rmPromises = []; for (var i = 0, ii = splices.length; i < ii; ++i) { var splice = splices[i]; var removed = splice.removed; - var viewIndex = this._getViewIndex(repeat, viewSlot, splice.index); - if (viewIndex >= 0) { - for (var j = 0, jj = removed.length; j < jj; ++j) { - var viewOrPromise = viewSlot.removeAt(viewIndex + removeDelta + rmPromises.length, true); - - var _length = viewSlot.children.length; - var overrideContext = _aureliaTemplatingResourcesRepeatUtilities.createFullOverrideContext(repeat, repeat.items[_length], _length, repeat.items.length); - var view = repeat.viewFactory.create(); - view.bind(overrideContext.bindingContext, overrideContext); - repeat.viewSlot.isAttached = false; - repeat.viewSlot.add(view); - repeat.viewSlot.isAttached = true; - - if (viewOrPromise instanceof Promise) { - rmPromises.push(viewOrPromise); - } + for (var j = 0, jj = removed.length; j < jj; ++j) { + var viewOrPromise = this._removeViewAt(repeat, splice.index + removeDelta + rmPromises.length, true); + if (viewOrPromise instanceof Promise) { + rmPromises.push(viewOrPromise); } - removeDelta -= splice.addedCount; } + removeDelta -= splice.addedCount; } if (rmPromises.length > 0) { - Promise.all(rmPromises).then(function () { - _this._handleAddedSplices(repeat, array, splices); - _this._updateViews(repeat, array, splices); + return Promise.all(rmPromises).then(function () { + _this3._handleAddedSplices(repeat, array, splices); + (0, _utilities.updateVirtualOverrideContexts)(repeat, 0); }); - } else { - this._handleAddedSplices(repeat, array, splices); - this._updateViews(repeat, array, splices); } + this._handleAddedSplices(repeat, array, splices); + (0, _utilities.updateVirtualOverrideContexts)(repeat, 0); }; - ArrayVirtualRepeatStrategy.prototype._updateViews = function _updateViews(repeat, items, splices) { - var totalAdded = 0; - var totalRemoved = 0; - repeat.items = items; - - for (var i = 0, ii = splices.length; i < ii; ++i) { - var splice = splices[0]; - totalAdded += splice.addedCount; - totalRemoved += splice.removed.length; + ArrayVirtualRepeatStrategy.prototype._removeViewAt = function _removeViewAt(repeat, collectionIndex, returnToCache) { + var viewOrPromise = void 0; + var view = void 0; + var viewSlot = repeat.viewSlot; + var viewCount = repeat.viewCount(); + var viewAddIndex = void 0; + + if (!this._isIndexBeforeViewSlot(repeat, viewSlot, collectionIndex) && !this._isIndexAfterViewSlot(repeat, viewSlot, collectionIndex)) { + var viewIndex = this._getViewIndex(repeat, viewSlot, collectionIndex); + viewOrPromise = repeat.removeView(viewIndex, returnToCache); + if (repeat.items.length > viewCount) { + var collectionAddIndex = void 0; + if (repeat._bottomBufferHeight > repeat.itemHeight) { + viewAddIndex = viewCount; + collectionAddIndex = repeat._getIndexOfLastView() + 1; + repeat._bottomBufferHeight = repeat._bottomBufferHeight - repeat.itemHeight; + } else if (repeat._topBufferHeight > 0) { + viewAddIndex = 0; + collectionAddIndex = repeat._getIndexOfFirstView() - 1; + repeat._topBufferHeight = repeat._topBufferHeight - repeat.itemHeight; + } + var data = repeat.items[collectionAddIndex]; + if (data) { + var overrideContext = (0, _repeatUtilities.createFullOverrideContext)(repeat, repeat.items[collectionAddIndex], collectionAddIndex, repeat.items.length); + view = repeat.viewFactory.create(); + view.bind(overrideContext.bindingContext, overrideContext); + } + } else { + return viewOrPromise; + } + } else if (this._isIndexBeforeViewSlot(repeat, viewSlot, collectionIndex)) { + if (repeat._bottomBufferHeight > 0) { + repeat._bottomBufferHeight = repeat._bottomBufferHeight - repeat.itemHeight; + (0, _utilities.rebindAndMoveView)(repeat, repeat.view(0), repeat.view(0).overrideContext.$index, true); + } else { + repeat._topBufferHeight = repeat._topBufferHeight - repeat.itemHeight; + } + } else if (this._isIndexAfterViewSlot(repeat, viewSlot, collectionIndex)) { + repeat._bottomBufferHeight = repeat._bottomBufferHeight - repeat.itemHeight; } - var index = repeat._getIndexOfFirstView() - totalRemoved; - - if (index < 0) { - index = 0; + if (viewOrPromise instanceof Promise) { + viewOrPromise.then(function () { + repeat.viewSlot.insert(viewAddIndex, view); + repeat._adjustBufferHeights(); + }); + return undefined; + } else if (view) { + repeat.viewSlot.insert(viewAddIndex, view); } - var viewSlot = repeat.viewSlot; - - for (var i = 0, ii = viewSlot.children.length; i < ii; ++i) { - var view = viewSlot.children[i]; - var nextIndex = index + i; - var itemsLength = items.length; - if (nextIndex <= itemsLength - 1) { - view.bindingContext[repeat.local] = items[nextIndex]; - _aureliaTemplatingResourcesRepeatUtilities.updateOverrideContext(view.overrideContext, nextIndex, itemsLength); - } - } + repeat._adjustBufferHeights(); + }; - var bufferDelta = repeat.itemHeight * totalAdded + repeat.itemHeight * -totalRemoved; + ArrayVirtualRepeatStrategy.prototype._isIndexBeforeViewSlot = function _isIndexBeforeViewSlot(repeat, viewSlot, index) { + var viewIndex = this._getViewIndex(repeat, viewSlot, index); + return viewIndex < 0; + }; - if (repeat._bottomBufferHeight + bufferDelta < 0) { - repeat._topBufferHeight = repeat._topBufferHeight + bufferDelta; - } else { - repeat._bottomBufferHeight = repeat._bottomBufferHeight + bufferDelta; - } + ArrayVirtualRepeatStrategy.prototype._isIndexAfterViewSlot = function _isIndexAfterViewSlot(repeat, viewSlot, index) { + var viewIndex = this._getViewIndex(repeat, viewSlot, index); + return viewIndex > repeat._viewsLength - 1; + }; - if (repeat._bottomBufferHeight > 0) { - repeat.isLastIndex = false; + ArrayVirtualRepeatStrategy.prototype._getViewIndex = function _getViewIndex(repeat, viewSlot, index) { + if (repeat.viewCount() === 0) { + return -1; } - repeat._adjustBufferHeights(); + var topBufferItems = repeat._topBufferHeight / repeat.itemHeight; + return index - topBufferItems; }; ArrayVirtualRepeatStrategy.prototype._handleAddedSplices = function _handleAddedSplices(repeat, array, splices) { - var spliceIndexLow = undefined; var arrayLength = array.length; + var viewSlot = repeat.viewSlot; for (var i = 0, ii = splices.length; i < ii; ++i) { var splice = splices[i]; var addIndex = splice.index; var end = splice.index + splice.addedCount; - - if (typeof spliceIndexLow === 'undefined' || spliceIndexLow === null || spliceIndexLow > splice.index) { - spliceIndexLow = addIndex; - } - for (; addIndex < end; ++addIndex) { - var overrideContext = _aureliaTemplatingResourcesRepeatUtilities.createFullOverrideContext(repeat, array[addIndex], addIndex, arrayLength); - var view = repeat.viewFactory.create(); - view.bind(overrideContext.bindingContext, overrideContext); - repeat.viewSlot.insert(addIndex, view); + var hasDistanceToBottomViewPort = (0, _utilities.getElementDistanceToBottomViewPort)(repeat.bottomBuffer.previousElementSibling) > 0; + if (repeat.viewCount() === 0 || !this._isIndexBeforeViewSlot(repeat, viewSlot, addIndex) && !this._isIndexAfterViewSlot(repeat, viewSlot, addIndex) || hasDistanceToBottomViewPort) { + var overrideContext = (0, _repeatUtilities.createFullOverrideContext)(repeat, array[addIndex], addIndex, arrayLength); + repeat.insertView(addIndex, overrideContext.bindingContext, overrideContext); + if (!repeat._hasCalculatedSizes) { + repeat._calcInitialHeights(1); + } else if (repeat.viewCount() > repeat._viewsLength) { + if (hasDistanceToBottomViewPort) { + repeat.removeView(0, true, true); + repeat._topBufferHeight = repeat._topBufferHeight + repeat.itemHeight; + repeat._adjustBufferHeights(); + } else { + repeat.removeView(repeat.viewCount() - 1, true, true); + repeat._bottomBufferHeight = repeat._bottomBufferHeight + repeat.itemHeight; + } + } + } else if (this._isIndexBeforeViewSlot(repeat, viewSlot, addIndex)) { + repeat._topBufferHeight = repeat._topBufferHeight + repeat.itemHeight; + } else if (this._isIndexAfterViewSlot(repeat, viewSlot, addIndex)) { + repeat._bottomBufferHeight = repeat._bottomBufferHeight + repeat.itemHeight; + repeat.isLastIndex = false; + } } } - - return spliceIndexLow; - }; - - ArrayVirtualRepeatStrategy.prototype._isIndexInDom = function _isIndexInDom(viewSlot, index) { - if (viewSlot.children.length === 0) { - return false; - } - - var indexLow = viewSlot.children[0].overrideContext.$index; - var indexHi = viewSlot.children[viewSlot.children.length - 1].overrideContext.$index; - - return index >= indexLow && index <= indexHi; - }; - - ArrayVirtualRepeatStrategy.prototype._getViewIndex = function _getViewIndex(repeat, viewSlot, index) { - if (viewSlot.children.length === 0) { - return -1; - } - var indexLow = viewSlot.children[0].overrideContext.$index; - var viewIndex = index - indexLow; - if (viewIndex > repeat._viewsLength - 1) { - viewIndex = -1; - } - return viewIndex; + repeat._adjustBufferHeights(); }; return ArrayVirtualRepeatStrategy; - })(_aureliaTemplatingResourcesArrayRepeatStrategy.ArrayRepeatStrategy); - - exports.ArrayVirtualRepeatStrategy = ArrayVirtualRepeatStrategy; + }(_arrayRepeatStrategy.ArrayRepeatStrategy); }); \ No newline at end of file diff --git a/dist/amd/aurelia-ui-virtualization.d.ts b/dist/amd/aurelia-ui-virtualization.d.ts new file mode 100644 index 0000000..cc50ca3 --- /dev/null +++ b/dist/amd/aurelia-ui-virtualization.d.ts @@ -0,0 +1,131 @@ +declare module 'aurelia-ui-virtualization' { + import { + updateOverrideContext, + createFullOverrideContext, + getItemsSourceExpression, + isOneTime, + unwrapExpression, + updateOneTimeBinding + } from 'aurelia-templating-resources/repeat-utilities'; + import { + ArrayRepeatStrategy + } from 'aurelia-templating-resources/array-repeat-strategy'; + import { + RepeatStrategyLocator + } from 'aurelia-templating-resources/repeat-strategy-locator'; + import { + inject + } from 'aurelia-dependency-injection'; + import { + ObserverLocator + } from 'aurelia-binding'; + import { + BoundViewFactory, + ViewSlot, + TargetInstruction, + customAttribute, + bindable, + templateController + } from 'aurelia-templating'; + import { + AbstractRepeater + } from 'aurelia-templating-resources'; + import { + viewsRequireLifecycle + } from 'aurelia-templating-resources/analyze-view-factory'; + export function calcOuterHeight(element: any): any; + export function insertBeforeNode(view: any, bottomBuffer: any): any; + + /** + * Update the override context. + * @param startIndex index in collection where to start updating. + */ + export function updateVirtualOverrideContexts(repeat: any, startIndex: any): any; + export function rebindAndMoveView(repeat: VirtualRepeat, view: View, index: number, moveToBottom: boolean): void; + export function getStyleValue(element: any, style: any): any; + export function getElementDistanceToBottomViewPort(element: any): any; + + /** + * A strategy for repeating a template over an array. + */ + export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy { + + // create first item to calculate the heights + createFirstItem(repeat: any): any; + + /** + * Handle the repeat's collection instance changing. + * @param repeat The repeater instance. + * @param items The new array instance. + */ + instanceChanged(repeat: any, items: any): any; + + /** + * Handle the repeat's collection instance mutating. + * @param repeat The repeat instance. + * @param array The modified array. + * @param splices Records of array changes. + */ + instanceMutated(repeat: any, array: any, splices: any): any; + } + export class ViewStrategyLocator { + getStrategy(element: any): any; + } + export class TableStrategy { + getScrollContainer(element: any): any; + moveViewFirst(view: any, topBuffer: any): any; + moveViewLast(view: any, bottomBuffer: any): any; + createTopBufferElement(element: any): any; + createBottomBufferElement(element: any): any; + removeBufferElements(element: any, topBuffer: any, bottomBuffer: any): any; + } + export class DefaultStrategy { + getScrollContainer(element: any): any; + moveViewFirst(view: any, topBuffer: any): any; + moveViewLast(view: any, bottomBuffer: any): any; + createTopBufferElement(element: any): any; + createBottomBufferElement(element: any): any; + removeBufferElements(element: any, topBuffer: any, bottomBuffer: any): any; + } + export class VirtualRepeatStrategyLocator extends RepeatStrategyLocator { + constructor(); + } + export class VirtualRepeat extends AbstractRepeater { + _first: any; + _previousFirst: any; + _viewsLength: any; + _lastRebind: any; + _topBufferHeight: any; + _bottomBufferHeight: any; + _bufferSize: any; + _scrollingDown: any; + _scrollingUp: any; + _switchedDirection: any; + _isAttached: any; + _ticking: any; + _fixedHeightContainer: any; + _hasCalculatedSizes: any; + _isAtTop: any; + items: any; + local: any; + constructor(element: any, viewFactory: any, instruction: any, viewSlot: any, observerLocator: any, strategyLocator: any, viewStrategyLocator: any); + attached(): any; + bind(bindingContext: any, overrideContext: any): any; + call(context: any, changes: any): any; + detached(): any; + itemsChanged(): any; + unbind(): any; + handleCollectionMutated(collection: any, changes: any): any; + handleInnerCollectionMutated(collection: any, changes: any): any; + + // @override AbstractRepeater + viewCount(): any; + views(): any; + view(index: any): any; + addView(bindingContext: any, overrideContext: any): any; + insertView(index: any, bindingContext: any, overrideContext: any): any; + removeAllViews(returnToCache: any, skipAnimation: any): any; + removeView(index: any, returnToCache: any, skipAnimation: any): any; + updateBindings(view: View): any; + } +} \ No newline at end of file diff --git a/dist/amd/index.js b/dist/amd/index.js index 5882b6a..fd730f6 100644 --- a/dist/amd/index.js +++ b/dist/amd/index.js @@ -1,13 +1,14 @@ -define(['exports', './virtual-repeat', './virtual-list'], function (exports, _virtualRepeat, _virtualList) { +define(['exports', './virtual-repeat'], function (exports, _virtualRepeat) { 'use strict'; - exports.__esModule = true; + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.VirtualRepeat = undefined; exports.configure = configure; - function configure(config) { - config.globalResources('./virtual-repeat', './virtual-list'); + config.globalResources('./virtual-repeat'); } exports.VirtualRepeat = _virtualRepeat.VirtualRepeat; - exports.VirtualList = _virtualList.VirtualList; }); \ No newline at end of file diff --git a/dist/amd/utilities.js b/dist/amd/utilities.js index e705748..f259ba3 100644 --- a/dist/amd/utilities.js +++ b/dist/amd/utilities.js @@ -1,41 +1,73 @@ -define(['exports'], function (exports) { +define(['exports', 'aurelia-templating-resources/repeat-utilities'], function (exports, _repeatUtilities) { 'use strict'; - exports.__esModule = true; + Object.defineProperty(exports, "__esModule", { + value: true + }); exports.calcOuterHeight = calcOuterHeight; - exports.calcScrollHeight = calcScrollHeight; exports.insertBeforeNode = insertBeforeNode; - + exports.updateVirtualOverrideContexts = updateVirtualOverrideContexts; + exports.rebindAndMoveView = rebindAndMoveView; + exports.getStyleValue = getStyleValue; + exports.getElementDistanceToBottomViewPort = getElementDistanceToBottomViewPort; function calcOuterHeight(element) { - var height; + var height = void 0; height = element.getBoundingClientRect().height; height += getStyleValue(element, 'marginTop'); height += getStyleValue(element, 'marginBottom'); return height; } - function calcScrollHeight(element) { - var height; - height = element.getBoundingClientRect().height; - height -= getStyleValue(element, 'borderTopWidth'); - height -= getStyleValue(element, 'borderBottomWidth'); - return height; - } - - function insertBeforeNode(view, scrollView, node) { + function insertBeforeNode(view, bottomBuffer) { var viewStart = view.firstChild; var element = viewStart.nextSibling; var viewEnd = view.lastChild; + var parentElement = bottomBuffer.parentElement; + + parentElement.insertBefore(viewEnd, bottomBuffer); + parentElement.insertBefore(element, viewEnd); + parentElement.insertBefore(viewStart, element); + } - scrollView.insertBefore(viewEnd, node); - scrollView.insertBefore(element, viewEnd); - scrollView.insertBefore(viewStart, element); + function updateVirtualOverrideContexts(repeat, startIndex) { + var views = repeat.viewSlot.children; + var viewLength = views.length; + var collectionLength = repeat.items.length; + + if (startIndex > 0) { + startIndex = startIndex - 1; + } + + var delta = repeat._topBufferHeight / repeat.itemHeight; + + for (; startIndex < viewLength; ++startIndex) { + (0, _repeatUtilities.updateOverrideContext)(views[startIndex].overrideContext, startIndex + delta, collectionLength); + } + } + + function rebindAndMoveView(repeat, view, index, moveToBottom) { + var items = repeat.items; + var viewSlot = repeat.viewSlot; + (0, _repeatUtilities.updateOverrideContext)(view.overrideContext, index, items.length); + view.bindingContext[repeat.local] = items[index]; + if (moveToBottom) { + viewSlot.children.push(viewSlot.children.shift()); + repeat.viewStrategy.moveViewLast(view, repeat.bottomBuffer); + } else { + viewSlot.children.unshift(viewSlot.children.splice(-1, 1)[0]); + repeat.viewStrategy.moveViewFirst(view, repeat.topBuffer); + } } function getStyleValue(element, style) { - var currentStyle, styleValue; + var currentStyle = void 0; + var styleValue = void 0; currentStyle = element.currentStyle || window.getComputedStyle(element); - styleValue = parseInt(currentStyle[style]); + styleValue = parseInt(currentStyle[style], 10); return Number.isNaN(styleValue) ? 0 : styleValue; } + + function getElementDistanceToBottomViewPort(element) { + return document.documentElement.clientHeight - element.getBoundingClientRect().bottom; + } }); \ No newline at end of file diff --git a/dist/amd/view-strategy.js b/dist/amd/view-strategy.js index 1e2ffdf..1b4d8db 100644 --- a/dist/amd/view-strategy.js +++ b/dist/amd/view-strategy.js @@ -62,9 +62,9 @@ define(['exports', './utilities'], function (exports, _utilities) { return buffer; }; - TableStrategy.prototype.removeBufferElements = function removeBufferElements(scrollList, topBuffer, bottomBuffer) { - scrollList.removeChild(topBuffer.parentElement); - scrollList.removeChild(bottomBuffer.parentElement); + TableStrategy.prototype.removeBufferElements = function removeBufferElements(element, topBuffer, bottomBuffer) { + element.parentElement.removeChild(topBuffer.parentElement); + element.parentElement.removeChild(bottomBuffer.parentElement); }; return TableStrategy; @@ -84,7 +84,9 @@ define(['exports', './utilities'], function (exports, _utilities) { }; DefaultStrategy.prototype.moveViewLast = function moveViewLast(view, bottomBuffer) { - (0, _utilities.insertBeforeNode)(view, bottomBuffer); + var previousSibling = bottomBuffer.previousSibling; + var referenceNode = previousSibling.nodeType === 8 && previousSibling.data === 'anchor' ? previousSibling : bottomBuffer; + (0, _utilities.insertBeforeNode)(view, referenceNode); }; DefaultStrategy.prototype.createTopBufferElement = function createTopBufferElement(element) { @@ -104,8 +106,8 @@ define(['exports', './utilities'], function (exports, _utilities) { }; DefaultStrategy.prototype.removeBufferElements = function removeBufferElements(element, topBuffer, bottomBuffer) { - element.removeChild(topBuffer); - element.removeChild(bottomBuffer); + element.parentElement.removeChild(topBuffer); + element.parentElement.removeChild(bottomBuffer); }; return DefaultStrategy; diff --git a/dist/amd/virtual-list.html b/dist/amd/virtual-list.html deleted file mode 100644 index fcba809..0000000 --- a/dist/amd/virtual-list.html +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/dist/amd/virtual-list.js b/dist/amd/virtual-list.js deleted file mode 100644 index 2a64896..0000000 --- a/dist/amd/virtual-list.js +++ /dev/null @@ -1,36 +0,0 @@ -define(['exports', 'aurelia-templating'], function (exports, _aureliaTemplating) { - 'use strict'; - - exports.__esModule = true; - - var _createDecoratedClass = (function () { function defineProperties(target, descriptors, initializers) { for (var i = 0; i < descriptors.length; i++) { var descriptor = descriptors[i]; var decorators = descriptor.decorators; var key = descriptor.key; delete descriptor.key; delete descriptor.decorators; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor || descriptor.initializer) descriptor.writable = true; if (decorators) { for (var f = 0; f < decorators.length; f++) { var decorator = decorators[f]; if (typeof decorator === 'function') { descriptor = decorator(target, key, descriptor) || descriptor; } else { throw new TypeError('The decorator for method ' + descriptor.key + ' is of the invalid type ' + typeof decorator); } } if (descriptor.initializer !== undefined) { initializers[key] = descriptor; continue; } } Object.defineProperty(target, key, descriptor); } } return function (Constructor, protoProps, staticProps, protoInitializers, staticInitializers) { if (protoProps) defineProperties(Constructor.prototype, protoProps, protoInitializers); if (staticProps) defineProperties(Constructor, staticProps, staticInitializers); return Constructor; }; })(); - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - - function _defineDecoratedPropertyDescriptor(target, key, descriptors) { var _descriptor = descriptors[key]; if (!_descriptor) return; var descriptor = {}; for (var _key in _descriptor) descriptor[_key] = _descriptor[_key]; descriptor.value = descriptor.initializer ? descriptor.initializer.call(target) : undefined; Object.defineProperty(target, key, descriptor); } - - var VirtualList = (function () { - var _instanceInitializers = {}; - - function VirtualList() { - _classCallCheck(this, VirtualList); - - _defineDecoratedPropertyDescriptor(this, 'items', _instanceInitializers); - } - - VirtualList.prototype.bind = function bind(bindingContext, overrideContext) { - this.$parent = bindingContext; - }; - - _createDecoratedClass(VirtualList, [{ - key: 'items', - decorators: [_aureliaTemplating.bindable], - initializer: null, - enumerable: true - }], null, _instanceInitializers); - - return VirtualList; - })(); - - exports.VirtualList = VirtualList; -}); \ No newline at end of file diff --git a/dist/amd/virtual-repeat-strategy-locator.js b/dist/amd/virtual-repeat-strategy-locator.js index 9aa7552..ca4570d 100644 --- a/dist/amd/virtual-repeat-strategy-locator.js +++ b/dist/amd/virtual-repeat-strategy-locator.js @@ -1,29 +1,58 @@ -define(['exports', 'aurelia-templating-resources/repeat-strategy-locator', './array-virtual-repeat-strategy'], function (exports, _aureliaTemplatingResourcesRepeatStrategyLocator, _arrayVirtualRepeatStrategy) { +define(['exports', 'aurelia-templating-resources/repeat-strategy-locator', './array-virtual-repeat-strategy'], function (exports, _repeatStrategyLocator, _arrayVirtualRepeatStrategy) { 'use strict'; - exports.__esModule = true; + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.VirtualRepeatStrategyLocator = undefined; - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + function _possibleConstructorReturn(self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + + return call && (typeof call === "object" || typeof call === "function") ? call : self; + } - function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + function _inherits(subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; + } - var VirtualRepeatStrategyLocator = (function (_RepeatStrategyLocator) { - _inherits(VirtualRepeatStrategyLocator, _RepeatStrategyLocator); + var VirtualRepeatStrategyLocator = exports.VirtualRepeatStrategyLocator = function (_RepeatStrategyLocato) { + _inherits(VirtualRepeatStrategyLocator, _RepeatStrategyLocato); function VirtualRepeatStrategyLocator() { _classCallCheck(this, VirtualRepeatStrategyLocator); - _RepeatStrategyLocator.call(this); - this.matchers = []; - this.strategies = []; + var _this = _possibleConstructorReturn(this, _RepeatStrategyLocato.call(this)); - this.addStrategy(function (items) { + _this.matchers = []; + _this.strategies = []; + + _this.addStrategy(function (items) { return items instanceof Array; }, new _arrayVirtualRepeatStrategy.ArrayVirtualRepeatStrategy()); + return _this; } return VirtualRepeatStrategyLocator; - })(_aureliaTemplatingResourcesRepeatStrategyLocator.RepeatStrategyLocator); - - exports.VirtualRepeatStrategyLocator = VirtualRepeatStrategyLocator; + }(_repeatStrategyLocator.RepeatStrategyLocator); }); \ No newline at end of file diff --git a/dist/amd/virtual-repeat.js b/dist/amd/virtual-repeat.js index 3f1d129..7bb4acb 100644 --- a/dist/amd/virtual-repeat.js +++ b/dist/amd/virtual-repeat.js @@ -1,4 +1,4 @@ -define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-templating', 'aurelia-templating-resources/repeat-utilities', 'aurelia-templating-resources/analyze-view-factory', './utilities', './virtual-repeat-strategy-locator', './view-strategy'], function (exports, _aureliaDependencyInjection, _aureliaBinding, _aureliaTemplating, _repeatUtilities, _analyzeViewFactory, _utilities, _virtualRepeatStrategyLocator, _viewStrategy) { +define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-templating', 'aurelia-templating-resources', 'aurelia-templating-resources/repeat-utilities', 'aurelia-templating-resources/analyze-view-factory', './utilities', './virtual-repeat-strategy-locator', './view-strategy'], function (exports, _aureliaDependencyInjection, _aureliaBinding, _aureliaTemplating, _aureliaTemplatingResources, _repeatUtilities, _analyzeViewFactory, _utilities, _virtualRepeatStrategyLocator, _viewStrategy) { 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -22,6 +22,30 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-t } } + function _possibleConstructorReturn(self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + + return call && (typeof call === "object" || typeof call === "function") ? call : self; + } + + function _inherits(subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; + } + function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { var desc = {}; Object['ke' + 'ys'](descriptor).forEach(function (key) { @@ -57,44 +81,51 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-t var _dec, _dec2, _class, _desc, _value, _class2, _descriptor, _descriptor2; - var VirtualRepeat = exports.VirtualRepeat = (_dec = (0, _aureliaTemplating.customAttribute)('virtual-repeat'), _dec2 = (0, _aureliaDependencyInjection.inject)(Element, _aureliaTemplating.BoundViewFactory, _aureliaTemplating.TargetInstruction, _aureliaTemplating.ViewSlot, _aureliaBinding.ObserverLocator, _virtualRepeatStrategyLocator.VirtualRepeatStrategyLocator, _viewStrategy.ViewStrategyLocator), _dec(_class = (0, _aureliaTemplating.templateController)(_class = _dec2(_class = (_class2 = function () { + var VirtualRepeat = exports.VirtualRepeat = (_dec = (0, _aureliaTemplating.customAttribute)('virtual-repeat'), _dec2 = (0, _aureliaDependencyInjection.inject)(Element, _aureliaTemplating.BoundViewFactory, _aureliaTemplating.TargetInstruction, _aureliaTemplating.ViewSlot, _aureliaBinding.ObserverLocator, _virtualRepeatStrategyLocator.VirtualRepeatStrategyLocator, _viewStrategy.ViewStrategyLocator), _dec(_class = (0, _aureliaTemplating.templateController)(_class = _dec2(_class = (_class2 = function (_AbstractRepeater) { + _inherits(VirtualRepeat, _AbstractRepeater); + function VirtualRepeat(element, viewFactory, instruction, viewSlot, observerLocator, strategyLocator, viewStrategyLocator) { _classCallCheck(this, VirtualRepeat); - this._first = 0; - this._previousFirst = 0; - this._viewsLength = 0; - this._lastRebind = 0; - this._topBufferHeight = 0; - this._bottomBufferHeight = 0; - this._bufferSize = 5; - this._scrollingDown = false; - this._scrollingUp = false; - this._switchedDirection = false; - this._isAttached = false; - this._ticking = false; - this._fixedHeightContainer = false; - this._hasCalculatedSizes = false; - - _initDefineProp(this, 'items', _descriptor, this); - - _initDefineProp(this, 'local', _descriptor2, this); - - this.element = element; - this.viewFactory = viewFactory; - this.instruction = instruction; - this.viewSlot = viewSlot; - this.observerLocator = observerLocator; - this.strategyLocator = strategyLocator; - this.viewStrategyLocator = viewStrategyLocator; - this.local = 'item'; - this.sourceExpression = (0, _repeatUtilities.getItemsSourceExpression)(this.instruction, 'virtual-repeat.for'); - this.isOneTime = (0, _repeatUtilities.isOneTime)(this.sourceExpression); - this.viewsRequireLifecycle = (0, _analyzeViewFactory.viewsRequireLifecycle)(viewFactory); + var _this = _possibleConstructorReturn(this, _AbstractRepeater.call(this, { + local: 'item', + viewsRequireLifecycle: (0, _analyzeViewFactory.viewsRequireLifecycle)(viewFactory) + })); + + _this._first = 0; + _this._previousFirst = 0; + _this._viewsLength = 0; + _this._lastRebind = 0; + _this._topBufferHeight = 0; + _this._bottomBufferHeight = 0; + _this._bufferSize = 5; + _this._scrollingDown = false; + _this._scrollingUp = false; + _this._switchedDirection = false; + _this._isAttached = false; + _this._ticking = false; + _this._fixedHeightContainer = false; + _this._hasCalculatedSizes = false; + _this._isAtTop = true; + + _initDefineProp(_this, 'items', _descriptor, _this); + + _initDefineProp(_this, 'local', _descriptor2, _this); + + _this.element = element; + _this.viewFactory = viewFactory; + _this.instruction = instruction; + _this.viewSlot = viewSlot; + _this.observerLocator = observerLocator; + _this.strategyLocator = strategyLocator; + _this.viewStrategyLocator = viewStrategyLocator; + _this.sourceExpression = (0, _repeatUtilities.getItemsSourceExpression)(_this.instruction, 'virtual-repeat.for'); + _this.isOneTime = (0, _repeatUtilities.isOneTime)(_this.sourceExpression); + return _this; } VirtualRepeat.prototype.attached = function attached() { - var _this = this; + var _this2 = this; this._isAttached = true; var element = this.element; @@ -104,7 +135,7 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-t this.bottomBuffer = this.viewStrategy.createBottomBufferElement(element); this.itemsChanged(); this.scrollListener = function () { - return _this._onScroll(); + return _this2._onScroll(); }; var containerStyle = this.scrollContainer.style; if (containerStyle.overflowY === 'scroll' || containerStyle.overflow === 'scroll' || containerStyle.overflowY === 'auto' || containerStyle.overflow === 'auto') { @@ -116,14 +147,8 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-t }; VirtualRepeat.prototype.bind = function bind(bindingContext, overrideContext) { - var _this2 = this; - this.scope = { bindingContext: bindingContext, overrideContext: overrideContext }; this._itemsLength = this.items.length; - - window.onresize = function () { - _this2._handleResize(); - }; }; VirtualRepeat.prototype.call = function call(context, changes) { @@ -131,8 +156,6 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-t }; VirtualRepeat.prototype.detached = function detached() { - window.onresize = null; - this.scrollContainer.removeEventListener('scroll', this.scrollListener); this._first = 0; this._previousFirst = 0; @@ -150,7 +173,7 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-t this.isLastIndex = false; this.scrollContainer = null; this.scrollContainerHeight = null; - this.viewSlot.removeAll(true); + this.removeAllViews(true); if (this.scrollHandler) { this.scrollHandler.dispose(); } @@ -230,14 +253,17 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-t var scrollTop = this._fixedHeightContainer ? this.scrollContainer.scrollTop : pageYOffset - this.topBuffer.offsetTop; this._first = Math.floor(scrollTop / itemHeight); this._first = this._first < 0 ? 0 : this._first; + if (this._first > this.items.length - this.elementsInView) { + this._first = this.items.length - this.elementsInView; + } this._checkScrolling(); - if (this._scrollingDown && (this._hasScrolledDownTheBuffer() || this._switchedDirection && this._hasScrolledDownTheBufferFromTop())) { + if (this._scrollingDown) { var viewsToMove = this._first - this._lastRebind; if (this._switchedDirection) { - viewsToMove = this.isAtTop ? this._first : this._bufferSize - (this._lastRebind - this._first); + viewsToMove = this._isAtTop ? this._first : this._bufferSize - (this._lastRebind - this._first); } - this.isAtTop = false; + this._isAtTop = false; this._lastRebind = this._first; var movedViewsCount = this._moveViews(viewsToMove); var adjustHeight = movedViewsCount < viewsToMove ? this._bottomBufferHeight : itemHeight * movedViewsCount; @@ -247,53 +273,32 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-t if (this._bottomBufferHeight >= 0) { this._adjustBufferHeights(); } - } else if (this._scrollingUp && (this._hasScrolledUpTheBuffer() || this._switchedDirection && this._hasScrolledUpTheBufferFromBottom())) { - var _viewsToMove = this._lastRebind - this._first; - if (this._switchedDirection) { - if (this.isLastIndex) { - _viewsToMove = this.items.length - this._first - this.elementsInView; - } else { - _viewsToMove = this._bufferSize - (this._first - this._lastRebind); - } - } - this.isLastIndex = false; - this._lastRebind = this._first; - var _movedViewsCount = this._moveViews(_viewsToMove); - this.movedViewsCount = _movedViewsCount; - var _adjustHeight = _movedViewsCount < _viewsToMove ? this._topBufferHeight : itemHeight * _movedViewsCount; - this._switchedDirection = false; - this._topBufferHeight = this._topBufferHeight - _adjustHeight; - this._bottomBufferHeight = this._bottomBufferHeight + _adjustHeight; - if (this._topBufferHeight >= 0) { - this._adjustBufferHeights(); + } else if (this._scrollingUp) { + var _viewsToMove = this._lastRebind - this._first; + if (this._switchedDirection) { + if (this.isLastIndex) { + _viewsToMove = this.items.length - this._first - this.elementsInView; + } else { + _viewsToMove = this._bufferSize - (this._first - this._lastRebind); } } + this.isLastIndex = false; + this._lastRebind = this._first; + var _movedViewsCount = this._moveViews(_viewsToMove); + this.movedViewsCount = _movedViewsCount; + var _adjustHeight = _movedViewsCount < _viewsToMove ? this._topBufferHeight : itemHeight * _movedViewsCount; + this._switchedDirection = false; + this._topBufferHeight = this._topBufferHeight - _adjustHeight; + this._bottomBufferHeight = this._bottomBufferHeight + _adjustHeight; + if (this._topBufferHeight >= 0) { + this._adjustBufferHeights(); + } + } this._previousFirst = this._first; this._ticking = false; }; - VirtualRepeat.prototype._handleResize = function _handleResize() { - var children = this.viewSlot.children; - var childrenLength = children.length; - var overrideContext = void 0; - var view = void 0; - var addIndex = void 0; - - this.scrollContainerHeight = calcScrollHeight(this.scrollContainer); - this._viewsLength = Math.ceil(this.scrollContainerHeight / this.itemHeight) + 1; - - if (this._viewsLength > childrenLength) { - addIndex = children[childrenLength - 1].overrideContext.$index + 1; - overrideContext = createFullOverrideContext(this, this.items[addIndex], addIndex, this.items.length); - view = this.viewFactory.create(); - view.bind(overrideContext.bindingContext, overrideContext); - this.viewSlot.insert(childrenLength, view); - } else if (this._viewsLength < childrenLength) { - this._viewsLength = childrenLength; - } - }; - VirtualRepeat.prototype._checkScrolling = function _checkScrolling() { if (this._first > this._previousFirst && (this._bottomBufferHeight > 0 || !this.isLastIndex)) { if (!this._scrollingDown) { @@ -304,7 +309,7 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-t this._switchedDirection = false; } this._isScrolling = true; - } else if (this._first < this._previousFirst && (this._topBufferHeight >= 0 || !this.isAtTop)) { + } else if (this._first < this._previousFirst && (this._topBufferHeight >= 0 || !this._isAtTop)) { if (!this._scrollingUp) { this._scrollingDown = false; this._scrollingUp = true; @@ -318,24 +323,6 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-t } }; - VirtualRepeat.prototype._hasScrolledDownTheBuffer = function _hasScrolledDownTheBuffer() { - var atBottom = this._first + this._viewsLength >= this.items.length; - var itemsAddedWhileAtBottom = atBottom && this._first > this._lastRebind; - return this._first - this._lastRebind >= this._bufferSize || itemsAddedWhileAtBottom; - }; - - VirtualRepeat.prototype._hasScrolledDownTheBufferFromTop = function _hasScrolledDownTheBufferFromTop() { - return this._first - this._bufferSize > 0; - }; - - VirtualRepeat.prototype._hasScrolledUpTheBuffer = function _hasScrolledUpTheBuffer() { - return this._lastRebind - this._first >= this._bufferSize; - }; - - VirtualRepeat.prototype._hasScrolledUpTheBufferFromBottom = function _hasScrolledUpTheBufferFromBottom() { - return this._first + this._bufferSize < this.items.length; - }; - VirtualRepeat.prototype._adjustBufferHeights = function _adjustBufferHeights() { this.topBuffer.setAttribute('style', 'height: ' + this._topBufferHeight + 'px'); this.bottomBuffer.setAttribute('style', 'height: ' + this._bottomBufferHeight + 'px'); @@ -358,35 +345,33 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-t return index - i; }; var isAtFirstOrLastIndex = function isAtFirstOrLastIndex() { - return _this5._scrollingDown ? _this5.isLastIndex : _this5.isAtTop; + return _this5._scrollingDown ? _this5.isLastIndex : _this5._isAtTop; }; - var viewSlot = this.viewSlot; - var childrenLength = viewSlot.children.length; + var childrenLength = this.viewCount(); var viewIndex = this._scrollingDown ? 0 : childrenLength - 1; var items = this.items; var index = this._scrollingDown ? this._getIndexOfLastView() + 1 : this._getIndexOfFirstView() - 1; var i = 0; while (i < length && !isAtFirstOrLastIndex()) { - var view = viewSlot.children[viewIndex]; + var view = this.view(viewIndex); var nextIndex = getNextIndex(index, i); this.isLastIndex = nextIndex >= items.length - 1; - this.isAtTop = nextIndex <= 0; - if (!isAtFirstOrLastIndex()) { + this._isAtTop = nextIndex <= 0; + if (!(isAtFirstOrLastIndex() && childrenLength >= items.length)) { (0, _utilities.rebindAndMoveView)(this, view, nextIndex, this._scrollingDown); i++; } } + return length - (length - i); }; VirtualRepeat.prototype._getIndexOfLastView = function _getIndexOfLastView() { - var children = this.viewSlot.children; - return children[children.length - 1].overrideContext.$index; + return this.view(this.viewCount() - 1).overrideContext.$index; }; VirtualRepeat.prototype._getIndexOfFirstView = function _getIndexOfFirstView() { - var children = this.viewSlot.children; - return children[0] ? children[0].overrideContext.$index : -1; + return this.view(0) ? this.view(0).overrideContext.$index : -1; }; VirtualRepeat.prototype._calcInitialHeights = function _calcInitialHeights(itemsLength) { @@ -395,7 +380,7 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-t } this._hasCalculatedSizes = true; this._itemsLength = itemsLength; - var firstViewElement = this.viewSlot.children[0].firstChild.nextElementSibling; + var firstViewElement = this.view(0).firstChild.nextElementSibling; this.itemHeight = (0, _utilities.calcOuterHeight)(firstViewElement); if (this.itemHeight <= 0) { throw new Error('Could not calculate item height'); @@ -455,8 +440,55 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-t } }; + VirtualRepeat.prototype.viewCount = function viewCount() { + return this.viewSlot.children.length; + }; + + VirtualRepeat.prototype.views = function views() { + return this.viewSlot.children; + }; + + VirtualRepeat.prototype.view = function view(index) { + return this.viewSlot.children[index]; + }; + + VirtualRepeat.prototype.addView = function addView(bindingContext, overrideContext) { + var view = this.viewFactory.create(); + view.bind(bindingContext, overrideContext); + this.viewSlot.add(view); + }; + + VirtualRepeat.prototype.insertView = function insertView(index, bindingContext, overrideContext) { + var view = this.viewFactory.create(); + view.bind(bindingContext, overrideContext); + this.viewSlot.insert(index, view); + }; + + VirtualRepeat.prototype.removeAllViews = function removeAllViews(returnToCache, skipAnimation) { + return this.viewSlot.removeAll(returnToCache, skipAnimation); + }; + + VirtualRepeat.prototype.removeView = function removeView(index, returnToCache, skipAnimation) { + return this.viewSlot.removeAt(index, returnToCache, skipAnimation); + }; + + VirtualRepeat.prototype.updateBindings = function updateBindings(view) { + var j = view.bindings.length; + while (j--) { + (0, _repeatUtilities.updateOneTimeBinding)(view.bindings[j]); + } + j = view.controllers.length; + while (j--) { + var k = view.controllers[j].boundProperties.length; + while (k--) { + var binding = view.controllers[j].boundProperties[k].binding; + (0, _repeatUtilities.updateOneTimeBinding)(binding); + } + } + }; + return VirtualRepeat; - }(), (_descriptor = _applyDecoratedDescriptor(_class2.prototype, 'items', [_aureliaTemplating.bindable], { + }(_aureliaTemplatingResources.AbstractRepeater), (_descriptor = _applyDecoratedDescriptor(_class2.prototype, 'items', [_aureliaTemplating.bindable], { enumerable: true, initializer: null }), _descriptor2 = _applyDecoratedDescriptor(_class2.prototype, 'local', [_aureliaTemplating.bindable], { diff --git a/dist/aurelia-ui-virtualization.d.ts b/dist/aurelia-ui-virtualization.d.ts new file mode 100644 index 0000000..cc50ca3 --- /dev/null +++ b/dist/aurelia-ui-virtualization.d.ts @@ -0,0 +1,131 @@ +declare module 'aurelia-ui-virtualization' { + import { + updateOverrideContext, + createFullOverrideContext, + getItemsSourceExpression, + isOneTime, + unwrapExpression, + updateOneTimeBinding + } from 'aurelia-templating-resources/repeat-utilities'; + import { + ArrayRepeatStrategy + } from 'aurelia-templating-resources/array-repeat-strategy'; + import { + RepeatStrategyLocator + } from 'aurelia-templating-resources/repeat-strategy-locator'; + import { + inject + } from 'aurelia-dependency-injection'; + import { + ObserverLocator + } from 'aurelia-binding'; + import { + BoundViewFactory, + ViewSlot, + TargetInstruction, + customAttribute, + bindable, + templateController + } from 'aurelia-templating'; + import { + AbstractRepeater + } from 'aurelia-templating-resources'; + import { + viewsRequireLifecycle + } from 'aurelia-templating-resources/analyze-view-factory'; + export function calcOuterHeight(element: any): any; + export function insertBeforeNode(view: any, bottomBuffer: any): any; + + /** + * Update the override context. + * @param startIndex index in collection where to start updating. + */ + export function updateVirtualOverrideContexts(repeat: any, startIndex: any): any; + export function rebindAndMoveView(repeat: VirtualRepeat, view: View, index: number, moveToBottom: boolean): void; + export function getStyleValue(element: any, style: any): any; + export function getElementDistanceToBottomViewPort(element: any): any; + + /** + * A strategy for repeating a template over an array. + */ + export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy { + + // create first item to calculate the heights + createFirstItem(repeat: any): any; + + /** + * Handle the repeat's collection instance changing. + * @param repeat The repeater instance. + * @param items The new array instance. + */ + instanceChanged(repeat: any, items: any): any; + + /** + * Handle the repeat's collection instance mutating. + * @param repeat The repeat instance. + * @param array The modified array. + * @param splices Records of array changes. + */ + instanceMutated(repeat: any, array: any, splices: any): any; + } + export class ViewStrategyLocator { + getStrategy(element: any): any; + } + export class TableStrategy { + getScrollContainer(element: any): any; + moveViewFirst(view: any, topBuffer: any): any; + moveViewLast(view: any, bottomBuffer: any): any; + createTopBufferElement(element: any): any; + createBottomBufferElement(element: any): any; + removeBufferElements(element: any, topBuffer: any, bottomBuffer: any): any; + } + export class DefaultStrategy { + getScrollContainer(element: any): any; + moveViewFirst(view: any, topBuffer: any): any; + moveViewLast(view: any, bottomBuffer: any): any; + createTopBufferElement(element: any): any; + createBottomBufferElement(element: any): any; + removeBufferElements(element: any, topBuffer: any, bottomBuffer: any): any; + } + export class VirtualRepeatStrategyLocator extends RepeatStrategyLocator { + constructor(); + } + export class VirtualRepeat extends AbstractRepeater { + _first: any; + _previousFirst: any; + _viewsLength: any; + _lastRebind: any; + _topBufferHeight: any; + _bottomBufferHeight: any; + _bufferSize: any; + _scrollingDown: any; + _scrollingUp: any; + _switchedDirection: any; + _isAttached: any; + _ticking: any; + _fixedHeightContainer: any; + _hasCalculatedSizes: any; + _isAtTop: any; + items: any; + local: any; + constructor(element: any, viewFactory: any, instruction: any, viewSlot: any, observerLocator: any, strategyLocator: any, viewStrategyLocator: any); + attached(): any; + bind(bindingContext: any, overrideContext: any): any; + call(context: any, changes: any): any; + detached(): any; + itemsChanged(): any; + unbind(): any; + handleCollectionMutated(collection: any, changes: any): any; + handleInnerCollectionMutated(collection: any, changes: any): any; + + // @override AbstractRepeater + viewCount(): any; + views(): any; + view(index: any): any; + addView(bindingContext: any, overrideContext: any): any; + insertView(index: any, bindingContext: any, overrideContext: any): any; + removeAllViews(returnToCache: any, skipAnimation: any): any; + removeView(index: any, returnToCache: any, skipAnimation: any): any; + updateBindings(view: View): any; + } +} \ No newline at end of file diff --git a/dist/aurelia-ui-virtualization.js b/dist/aurelia-ui-virtualization.js new file mode 100644 index 0000000..49e9bad --- /dev/null +++ b/dist/aurelia-ui-virtualization.js @@ -0,0 +1,783 @@ +import {updateOverrideContext,createFullOverrideContext,getItemsSourceExpression,isOneTime,unwrapExpression,updateOneTimeBinding} from 'aurelia-templating-resources/repeat-utilities'; +import {ArrayRepeatStrategy} from 'aurelia-templating-resources/array-repeat-strategy'; +import {RepeatStrategyLocator} from 'aurelia-templating-resources/repeat-strategy-locator'; +import {inject} from 'aurelia-dependency-injection'; +import {ObserverLocator} from 'aurelia-binding'; +import {BoundViewFactory,ViewSlot,TargetInstruction,customAttribute,bindable,templateController} from 'aurelia-templating'; +import {AbstractRepeater} from 'aurelia-templating-resources'; +import {viewsRequireLifecycle} from 'aurelia-templating-resources/analyze-view-factory'; + +export function calcOuterHeight(element) { + let height; + height = element.getBoundingClientRect().height; + height += getStyleValue(element, 'marginTop'); + height += getStyleValue(element, 'marginBottom'); + return height; +} + +export function insertBeforeNode(view, bottomBuffer) { + let viewStart = view.firstChild; + let element = viewStart.nextSibling; + let viewEnd = view.lastChild; + let parentElement = bottomBuffer.parentElement; + + parentElement.insertBefore(viewEnd, bottomBuffer); + parentElement.insertBefore(element, viewEnd); + parentElement.insertBefore(viewStart, element); +} + +/** +* Update the override context. +* @param startIndex index in collection where to start updating. +*/ +export function updateVirtualOverrideContexts(repeat, startIndex) { + let views = repeat.viewSlot.children; + let viewLength = views.length; + let collectionLength = repeat.items.length; + + if (startIndex > 0) { + startIndex = startIndex - 1; + } + + let delta = repeat._topBufferHeight / repeat.itemHeight; + + for (; startIndex < viewLength; ++startIndex) { + updateOverrideContext(views[startIndex].overrideContext, startIndex + delta, collectionLength); + } +} + +export function rebindAndMoveView(repeat: VirtualRepeat, view: View, index: number, moveToBottom: boolean): void { + let items = repeat.items; + let viewSlot = repeat.viewSlot; + updateOverrideContext(view.overrideContext, index, items.length); + view.bindingContext[repeat.local] = items[index]; + if (moveToBottom) { + viewSlot.children.push(viewSlot.children.shift()); + repeat.viewStrategy.moveViewLast(view, repeat.bottomBuffer); + } else { + viewSlot.children.unshift(viewSlot.children.splice(-1, 1)[0]); + repeat.viewStrategy.moveViewFirst(view, repeat.topBuffer); + } +} + +export function getStyleValue(element, style) { + let currentStyle; + let styleValue; + currentStyle = element.currentStyle || window.getComputedStyle(element); + styleValue = parseInt(currentStyle[style], 10); + return Number.isNaN(styleValue) ? 0 : styleValue; +} + +export function getElementDistanceToBottomViewPort(element) { + return document.documentElement.clientHeight - element.getBoundingClientRect().bottom; +} + +/** +* A strategy for repeating a template over an array. +*/ +export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy { + // create first item to calculate the heights + createFirstItem(repeat) { + let overrideContext = createFullOverrideContext(repeat, repeat.items[0], 0, 1); + repeat.addView(overrideContext.bindingContext, overrideContext); + } + /** + * Handle the repeat's collection instance changing. + * @param repeat The repeater instance. + * @param items The new array instance. + */ + instanceChanged(repeat, items) { + this._inPlaceProcessItems(repeat, items); + } + + _standardProcessInstanceChanged(repeat, items) { + for (let i = 1, ii = repeat._viewsLength; i < ii; ++i) { + let overrideContext = createFullOverrideContext(repeat, items[i], i, ii); + repeat.addView(overrideContext.bindingContext, overrideContext); + } + } + + _inPlaceProcessItems(repeat, items) { + let itemsLength = items.length; + let viewsLength = repeat.viewCount(); + let first = repeat._getIndexOfFirstView(); + // remove unneeded views. + while (viewsLength > repeat._viewsLength) { + viewsLength--; + repeat.removeView(viewsLength, true); + } + // avoid repeated evaluating the property-getter for the "local" property. + let local = repeat.local; + // re-evaluate bindings on existing views. + for (let i = 0; i < viewsLength; i++) { + let view = repeat.view(i); + let last = i === itemsLength - 1; + let middle = i !== 0 && !last; + // any changes to the binding context? + if (view.bindingContext[local] === items[i + first] && view.overrideContext.$middle === middle && view.overrideContext.$last === last) { + // no changes. continue... + continue; + } + // update the binding context and refresh the bindings. + view.bindingContext[local] = items[i + first]; + view.overrideContext.$middle = middle; + view.overrideContext.$last = last; + repeat.updateBindings(view); + } + // add new views + let minLength = Math.min(repeat._viewsLength, items.length); + for (let i = viewsLength; i < minLength; i++) { + let overrideContext = createFullOverrideContext(repeat, items[i], i, itemsLength); + repeat.addView(overrideContext.bindingContext, overrideContext); + } + } + + /** + * Handle the repeat's collection instance mutating. + * @param repeat The repeat instance. + * @param array The modified array. + * @param splices Records of array changes. + */ + instanceMutated(repeat, array, splices) { + this._standardProcessInstanceMutated(repeat, array, splices); + } + + _standardProcessInstanceMutated(repeat, array, splices) { + if (repeat.__queuedSplices) { + for (let i = 0, ii = splices.length; i < ii; ++i) { + let {index, removed, addedCount} = splices[i]; + mergeSplice(repeat.__queuedSplices, index, removed, addedCount); + } + repeat.__array = array.slice(0); + return; + } + + let maybePromise = this._runSplices(repeat, array.slice(0), splices); + if (maybePromise instanceof Promise) { + let queuedSplices = repeat.__queuedSplices = []; + + let runQueuedSplices = () => { + if (! queuedSplices.length) { + delete repeat.__queuedSplices; + delete repeat.__array; + return; + } + + let nextPromise = this._runSplices(repeat, repeat.__array, queuedSplices) || Promise.resolve(); + nextPromise.then(runQueuedSplices); + }; + + maybePromise.then(runQueuedSplices); + } + } + + _runSplices(repeat, array, splices) { + let removeDelta = 0; + let rmPromises = []; + + for (let i = 0, ii = splices.length; i < ii; ++i) { + let splice = splices[i]; + let removed = splice.removed; + for (let j = 0, jj = removed.length; j < jj; ++j) { + let viewOrPromise = this._removeViewAt(repeat, splice.index + removeDelta + rmPromises.length, true); + if (viewOrPromise instanceof Promise) { + rmPromises.push(viewOrPromise); + } + } + removeDelta -= splice.addedCount; + } + + if (rmPromises.length > 0) { + return Promise.all(rmPromises).then(() => { + this._handleAddedSplices(repeat, array, splices); + updateVirtualOverrideContexts(repeat, 0); + }); + } + this._handleAddedSplices(repeat, array, splices); + updateVirtualOverrideContexts(repeat, 0); + } + + _removeViewAt(repeat, collectionIndex, returnToCache) { + let viewOrPromise; + let view; + let viewSlot = repeat.viewSlot; + let viewCount = repeat.viewCount(); + let viewAddIndex; + // index in view slot? + if (!this._isIndexBeforeViewSlot(repeat, viewSlot, collectionIndex) && !this._isIndexAfterViewSlot(repeat, viewSlot, collectionIndex)) { + let viewIndex = this._getViewIndex(repeat, viewSlot, collectionIndex); + viewOrPromise = repeat.removeView(viewIndex, returnToCache); + if (repeat.items.length > viewCount) { + // TODO: do not trigger view lifecycle here + let collectionAddIndex; + if (repeat._bottomBufferHeight > repeat.itemHeight) { + viewAddIndex = viewCount; + collectionAddIndex = repeat._getIndexOfLastView() + 1; + repeat._bottomBufferHeight = repeat._bottomBufferHeight - (repeat.itemHeight); + } else if (repeat._topBufferHeight > 0) { + viewAddIndex = 0; + collectionAddIndex = repeat._getIndexOfFirstView() - 1; + repeat._topBufferHeight = repeat._topBufferHeight - (repeat.itemHeight); + } + let data = repeat.items[collectionAddIndex]; + if (data) { + let overrideContext = createFullOverrideContext(repeat, repeat.items[collectionAddIndex], collectionAddIndex, repeat.items.length); + view = repeat.viewFactory.create(); + view.bind(overrideContext.bindingContext, overrideContext); + } + } else { + return viewOrPromise; + } + } else if (this._isIndexBeforeViewSlot(repeat, viewSlot, collectionIndex)) { + if (repeat._bottomBufferHeight > 0) { + repeat._bottomBufferHeight = repeat._bottomBufferHeight - (repeat.itemHeight); + rebindAndMoveView(repeat, repeat.view(0), repeat.view(0).overrideContext.$index, true); + } else { + repeat._topBufferHeight = repeat._topBufferHeight - (repeat.itemHeight); + } + } else if (this._isIndexAfterViewSlot(repeat, viewSlot, collectionIndex)) { + repeat._bottomBufferHeight = repeat._bottomBufferHeight - (repeat.itemHeight); + } + + if (viewOrPromise instanceof Promise) { + viewOrPromise.then(() => { + repeat.viewSlot.insert(viewAddIndex, view); + repeat._adjustBufferHeights(); + }); + return undefined; + } else if (view) { + repeat.viewSlot.insert(viewAddIndex, view); + } + + repeat._adjustBufferHeights(); + } + + _isIndexBeforeViewSlot(repeat: VirtualRepeat, viewSlot: ViewSlot, index: number): number { + let viewIndex = this._getViewIndex(repeat, viewSlot, index); + return viewIndex < 0; + } + + _isIndexAfterViewSlot(repeat: VirtualRepeat, viewSlot: ViewSlot, index: number): number { + let viewIndex = this._getViewIndex(repeat, viewSlot, index); + return viewIndex > repeat._viewsLength - 1; + } + + _getViewIndex(repeat: VirtualRepeat, viewSlot: ViewSlot, index: number): number { + if (repeat.viewCount() === 0) { + return -1; + } + + let topBufferItems = repeat._topBufferHeight / repeat.itemHeight; + return index - topBufferItems; + } + + _handleAddedSplices(repeat, array, splices) { + let arrayLength = array.length; + let viewSlot = repeat.viewSlot; + for (let i = 0, ii = splices.length; i < ii; ++i) { + let splice = splices[i]; + let addIndex = splice.index; + let end = splice.index + splice.addedCount; + for (; addIndex < end; ++addIndex) { + let hasDistanceToBottomViewPort = getElementDistanceToBottomViewPort(repeat.bottomBuffer.previousElementSibling) > 0; + if (repeat.viewCount() === 0 || (!this._isIndexBeforeViewSlot(repeat, viewSlot, addIndex) && !this._isIndexAfterViewSlot(repeat, viewSlot, addIndex)) || hasDistanceToBottomViewPort) { + let overrideContext = createFullOverrideContext(repeat, array[addIndex], addIndex, arrayLength); + repeat.insertView(addIndex, overrideContext.bindingContext, overrideContext); + if (!repeat._hasCalculatedSizes) { + repeat._calcInitialHeights(1); + } else if (repeat.viewCount() > repeat._viewsLength) { + if (hasDistanceToBottomViewPort) { + repeat.removeView(0, true, true); + repeat._topBufferHeight = repeat._topBufferHeight + repeat.itemHeight; + repeat._adjustBufferHeights(); + } else { + repeat.removeView(repeat.viewCount() - 1, true, true); + repeat._bottomBufferHeight = repeat._bottomBufferHeight + repeat.itemHeight; + } + } + } else if (this._isIndexBeforeViewSlot(repeat, viewSlot, addIndex)) { + repeat._topBufferHeight = repeat._topBufferHeight + repeat.itemHeight; + } else if (this._isIndexAfterViewSlot(repeat, viewSlot, addIndex)) { + repeat._bottomBufferHeight = repeat._bottomBufferHeight + repeat.itemHeight; + repeat.isLastIndex = false; + } + } + } + repeat._adjustBufferHeights(); + } +} + +export class ViewStrategyLocator { + getStrategy(element) { + if (element.parentNode.localName === 'tbody') { + return new TableStrategy(); + } + return new DefaultStrategy(); + } +} + +export class TableStrategy { + getScrollContainer(element) { + return element.parentNode; + } + + moveViewFirst(view, topBuffer) { + insertBeforeNode(view, topBuffer.parentElement.nextElementSibling.previousSibling); + } + + moveViewLast(view, bottomBuffer) { + insertBeforeNode(view, bottomBuffer.parentElement); + } + + createTopBufferElement(element) { + let tr = document.createElement('tr'); + let buffer = document.createElement('td'); + buffer.setAttribute('style', 'height: 0px'); + tr.appendChild(buffer); + element.parentElement.insertBefore(tr, element); + return buffer; + } + + createBottomBufferElement(element) { + let tr = document.createElement('tr'); + let buffer = document.createElement('td'); + buffer.setAttribute('style', 'height: 0px'); + tr.appendChild(buffer); + element.parentNode.insertBefore(tr, element.nextSibling); + return buffer; + } + + removeBufferElements(element, topBuffer, bottomBuffer) { + element.parentElement.removeChild(topBuffer.parentElement); + element.parentElement.removeChild(bottomBuffer.parentElement); + } +} + +export class DefaultStrategy { + getScrollContainer(element) { + return element.parentNode; + } + + moveViewFirst(view, topBuffer) { + insertBeforeNode(view, topBuffer.nextElementSibling.previousSibling); + } + + moveViewLast(view, bottomBuffer) { + let previousSibling = bottomBuffer.previousSibling; + let referenceNode = previousSibling.nodeType === 8 && previousSibling.data === 'anchor' ? previousSibling : bottomBuffer; + insertBeforeNode(view, referenceNode); + } + + createTopBufferElement(element) { + let elementName = element.parentElement.localName === 'ul' ? 'li' : 'div'; + let buffer = document.createElement(elementName); + buffer.setAttribute('style', 'height: 0px'); + element.parentElement.insertBefore(buffer, element); + return buffer; + } + + createBottomBufferElement(element) { + let elementName = element.parentElement.localName === 'ul' ? 'li' : 'div'; + let buffer = document.createElement(elementName); + buffer.setAttribute('style', 'height: 0px'); + element.parentNode.insertBefore(buffer, element.nextSibling); + return buffer; + } + + removeBufferElements(element, topBuffer, bottomBuffer) { + element.parentElement.removeChild(topBuffer); + element.parentElement.removeChild(bottomBuffer); + } +} + +export class VirtualRepeatStrategyLocator extends RepeatStrategyLocator { + constructor() { + super(); + this.matchers = []; + this.strategies = []; + + this.addStrategy(items => items instanceof Array, new ArrayVirtualRepeatStrategy()); + } +} + +@customAttribute('virtual-repeat') +@templateController +@inject(Element, BoundViewFactory, TargetInstruction, ViewSlot, ObserverLocator, VirtualRepeatStrategyLocator, ViewStrategyLocator) +export class VirtualRepeat extends AbstractRepeater { + _first = 0; + _previousFirst = 0; + _viewsLength = 0; + _lastRebind = 0; + _topBufferHeight = 0; + _bottomBufferHeight = 0; + _bufferSize = 5; + _scrollingDown = false; + _scrollingUp = false; + _switchedDirection = false; + _isAttached = false; + _ticking = false; + _fixedHeightContainer = false; + _hasCalculatedSizes = false; + _isAtTop = true; + + @bindable items + @bindable local + constructor(element, viewFactory, instruction, viewSlot, observerLocator, strategyLocator, viewStrategyLocator) { + super({ + local: 'item', + viewsRequireLifecycle: viewsRequireLifecycle(viewFactory) + }); + + this.element = element; + this.viewFactory = viewFactory; + this.instruction = instruction; + this.viewSlot = viewSlot; + this.observerLocator = observerLocator; + this.strategyLocator = strategyLocator; + this.viewStrategyLocator = viewStrategyLocator; + this.sourceExpression = getItemsSourceExpression(this.instruction, 'virtual-repeat.for'); + this.isOneTime = isOneTime(this.sourceExpression); + } + + attached() { + this._isAttached = true; + let element = this.element; + this.viewStrategy = this.viewStrategyLocator.getStrategy(element); + this.scrollContainer = this.viewStrategy.getScrollContainer(element); + this.topBuffer = this.viewStrategy.createTopBufferElement(element); + this.bottomBuffer = this.viewStrategy.createBottomBufferElement(element); + this.itemsChanged(); + this.scrollListener = () => this._onScroll(); + let containerStyle = this.scrollContainer.style; + if (containerStyle.overflowY === 'scroll' || containerStyle.overflow === 'scroll' || containerStyle.overflowY === 'auto' || containerStyle.overflow === 'auto') { + this._fixedHeightContainer = true; + this.scrollContainer.addEventListener('scroll', this.scrollListener); + } else { + document.addEventListener('scroll', this.scrollListener); + } + } + + bind(bindingContext, overrideContext) { + this.scope = { bindingContext, overrideContext }; + this._itemsLength = this.items.length; + } + + call(context, changes) { + this[context](this.items, changes); + } + + detached() { + this.scrollContainer.removeEventListener('scroll', this.scrollListener); + this._first = 0; + this._previousFirst = 0; + this._viewsLength = 0; + this._lastRebind = 0; + this._topBufferHeight = 0; + this._bottomBufferHeight = 0; + this._scrollingDown = false; + this._scrollingUp = false; + this._switchedDirection = false; + this._isAttached = false; + this._ticking = false; + this._hasCalculatedSizes = false; + this.viewStrategy.removeBufferElements(this.element, this.topBuffer, this.bottomBuffer); + this.isLastIndex = false; + this.scrollContainer = null; + this.scrollContainerHeight = null; + this.removeAllViews(true); + if (this.scrollHandler) { + this.scrollHandler.dispose(); + } + this._unsubscribeCollection(); + } + + itemsChanged() { + this._unsubscribeCollection(); + // still bound? + if (!this.scope) { + return; + } + let items = this.items; + this.strategy = this.strategyLocator.getStrategy(items); + if (items.length > 0) { + this.strategy.createFirstItem(this); + } + this._calcInitialHeights(items.length); + if (!this.isOneTime && !this._observeInnerCollection()) { + this._observeCollection(); + } + + this.strategy.instanceChanged(this, items, this._viewsLength); + } + + unbind() { + this.scope = null; + this.items = null; + this._itemsLength = null; + } + + handleCollectionMutated(collection, changes) { + this._handlingMutations = true; + this._itemsLength = collection.length; + this.strategy.instanceMutated(this, collection, changes); + } + + handleInnerCollectionMutated(collection, changes) { + // guard against source expressions that have observable side-effects that could + // cause an infinite loop- eg a value converter that mutates the source array. + if (this.ignoreMutation) { + return; + } + this.ignoreMutation = true; + let newItems = this.sourceExpression.evaluate(this.scope, this.lookupFunctions); + this.observerLocator.taskQueue.queueMicroTask(() => this.ignoreMutation = false); + + // call itemsChanged... + if (newItems === this.items) { + // call itemsChanged directly. + this.itemsChanged(); + } else { + // call itemsChanged indirectly by assigning the new collection value to + // the items property, which will trigger the self-subscriber to call itemsChanged. + this.items = newItems; + } + } + + _onScroll() { + if (!this._ticking && !this._handlingMutations) { + requestAnimationFrame(() => this._handleScroll()); + this._ticking = true; + } + + if (this._handlingMutations) { + this._handlingMutations = false; + } + } + + _handleScroll() { + if (!this._isAttached) { + return; + } + let itemHeight = this.itemHeight; + let scrollTop = this._fixedHeightContainer ? this.scrollContainer.scrollTop : pageYOffset - this.topBuffer.offsetTop; + this._first = Math.floor(scrollTop / itemHeight); + this._first = this._first < 0 ? 0 : this._first; + if (this._first > this.items.length - this.elementsInView) { + this._first = this.items.length - this.elementsInView; + } + this._checkScrolling(); + // TODO if and else paths do almost same thing, refactor? + if (this._scrollingDown) { + let viewsToMove = this._first - this._lastRebind; + if (this._switchedDirection) { + viewsToMove = this._isAtTop ? this._first : this._bufferSize - (this._lastRebind - this._first); + } + this._isAtTop = false; + this._lastRebind = this._first; + let movedViewsCount = this._moveViews(viewsToMove); + let adjustHeight = movedViewsCount < viewsToMove ? this._bottomBufferHeight : itemHeight * movedViewsCount; + this._switchedDirection = false; + this._topBufferHeight = this._topBufferHeight + adjustHeight; + this._bottomBufferHeight = this._bottomBufferHeight - adjustHeight; + if (this._bottomBufferHeight >= 0) { + this._adjustBufferHeights(); + } + } else if (this._scrollingUp) { + let viewsToMove = this._lastRebind - this._first; + if (this._switchedDirection) { + if (this.isLastIndex) { + viewsToMove = this.items.length - this._first - this.elementsInView; + } else { + viewsToMove = this._bufferSize - (this._first - this._lastRebind); + } + } + this.isLastIndex = false; + this._lastRebind = this._first; + let movedViewsCount = this._moveViews(viewsToMove); + this.movedViewsCount = movedViewsCount; + let adjustHeight = movedViewsCount < viewsToMove ? this._topBufferHeight : itemHeight * movedViewsCount; + this._switchedDirection = false; + this._topBufferHeight = this._topBufferHeight - adjustHeight; + this._bottomBufferHeight = this._bottomBufferHeight + adjustHeight; + if (this._topBufferHeight >= 0) { + this._adjustBufferHeights(); + } + } + this._previousFirst = this._first; + + this._ticking = false; + } + + _checkScrolling() { + if (this._first > this._previousFirst && (this._bottomBufferHeight > 0 || !this.isLastIndex)) { + if (!this._scrollingDown) { + this._scrollingDown = true; + this._scrollingUp = false; + this._switchedDirection = true; + } else { + this._switchedDirection = false; + } + this._isScrolling = true; + } else if (this._first < this._previousFirst && (this._topBufferHeight >= 0 || !this._isAtTop)) { + if (!this._scrollingUp) { + this._scrollingDown = false; + this._scrollingUp = true; + this._switchedDirection = true; + } else { + this._switchedDirection = false; + } + this._isScrolling = true; + } else { + this._isScrolling = false; + } + } + + _adjustBufferHeights() { + this.topBuffer.setAttribute('style', `height: ${this._topBufferHeight}px`); + this.bottomBuffer.setAttribute('style', `height: ${this._bottomBufferHeight}px`); + } + + _unsubscribeCollection() { + if (this.collectionObserver) { + this.collectionObserver.unsubscribe(this.callContext, this); + this.collectionObserver = null; + this.callContext = null; + } + } + + _moveViews(length) { + let getNextIndex = this._scrollingDown ? (index, i) => index + i : (index, i) => index - i; + let isAtFirstOrLastIndex = () => this._scrollingDown ? this.isLastIndex : this._isAtTop; + let childrenLength = this.viewCount(); + let viewIndex = this._scrollingDown ? 0 : childrenLength - 1; + let items = this.items; + let index = this._scrollingDown ? this._getIndexOfLastView() + 1 : this._getIndexOfFirstView() - 1; + let i = 0; + while (i < length && !isAtFirstOrLastIndex()) { + let view = this.view(viewIndex); + let nextIndex = getNextIndex(index, i); + this.isLastIndex = nextIndex >= items.length - 1; + this._isAtTop = nextIndex <= 0; + if (!(isAtFirstOrLastIndex() && childrenLength >= items.length)) { + rebindAndMoveView(this, view, nextIndex, this._scrollingDown); + i++; + } + } + + return length - (length - i); + } + + _getIndexOfLastView() { + return this.view(this.viewCount() - 1).overrideContext.$index; + } + + _getIndexOfFirstView() { + return this.view(0) ? this.view(0).overrideContext.$index : -1; + } + + _calcInitialHeights(itemsLength: number) { + if (this._viewsLength > 0 && this._itemsLength === itemsLength || itemsLength <= 0) { + return; + } + this._hasCalculatedSizes = true; + this._itemsLength = itemsLength; + let firstViewElement = this.view(0).firstChild.nextElementSibling; + this.itemHeight = calcOuterHeight(firstViewElement); + if (this.itemHeight <= 0) { + throw new Error('Could not calculate item height'); + } + this.scrollContainerHeight = this._fixedHeightContainer ? this._calcScrollHeight(this.scrollContainer) : document.documentElement.clientHeight; + this.elementsInView = Math.ceil(this.scrollContainerHeight / this.itemHeight) + 1; + this._viewsLength = (this.elementsInView * 2) + this._bufferSize; + this._bottomBufferHeight = this.itemHeight * itemsLength - this.itemHeight * this._viewsLength; + if (this._bottomBufferHeight < 0) { + this._bottomBufferHeight = 0; + } + this.bottomBuffer.setAttribute('style', `height: ${this._bottomBufferHeight}px`); + this._topBufferHeight = 0; + this.topBuffer.setAttribute('style', `height: ${this._topBufferHeight}px`); + // TODO This will cause scrolling back to top when swapping collection instances that have different lengths - instead should keep the scroll position + this.scrollContainer.scrollTop = 0; + this._first = 0; + } + + _calcScrollHeight(element) { + let height; + height = element.getBoundingClientRect().height; + height -= getStyleValue(element, 'borderTopWidth'); + height -= getStyleValue(element, 'borderBottomWidth'); + return height; + } + + _observeInnerCollection() { + let items = this._getInnerCollection(); + let strategy = this.strategyLocator.getStrategy(items); + if (!strategy) { + return false; + } + this.collectionObserver = strategy.getCollectionObserver(this.observerLocator, items); + if (!this.collectionObserver) { + return false; + } + this.callContext = 'handleInnerCollectionMutated'; + this.collectionObserver.subscribe(this.callContext, this); + return true; + } + + _getInnerCollection() { + let expression = unwrapExpression(this.sourceExpression); + if (!expression) { + return null; + } + return expression.evaluate(this.scope, null); + } + + _observeCollection() { + let items = this.items; + this.collectionObserver = this.strategy.getCollectionObserver(this.observerLocator, items); + if (this.collectionObserver) { + this.callContext = 'handleCollectionMutated'; + this.collectionObserver.subscribe(this.callContext, this); + } + } + + // @override AbstractRepeater + viewCount() { return this.viewSlot.children.length; } + views() { return this.viewSlot.children; } + view(index) { return this.viewSlot.children[index]; } + + addView(bindingContext, overrideContext) { + let view = this.viewFactory.create(); + view.bind(bindingContext, overrideContext); + this.viewSlot.add(view); + } + + insertView(index, bindingContext, overrideContext) { + let view = this.viewFactory.create(); + view.bind(bindingContext, overrideContext); + this.viewSlot.insert(index, view); + } + + removeAllViews(returnToCache, skipAnimation) { + return this.viewSlot.removeAll(returnToCache, skipAnimation); + } + + removeView(index, returnToCache, skipAnimation) { + return this.viewSlot.removeAt(index, returnToCache, skipAnimation); + } + + updateBindings(view: View) { + let j = view.bindings.length; + while (j--) { + updateOneTimeBinding(view.bindings[j]); + } + j = view.controllers.length; + while (j--) { + let k = view.controllers[j].boundProperties.length; + while (k--) { + let binding = view.controllers[j].boundProperties[k].binding; + updateOneTimeBinding(binding); + } + } + } +} diff --git a/dist/commonjs/array-virtual-repeat-strategy.js b/dist/commonjs/array-virtual-repeat-strategy.js index 0caabe3..59d4f2b 100644 --- a/dist/commonjs/array-virtual-repeat-strategy.js +++ b/dist/commonjs/array-virtual-repeat-strategy.js @@ -1,29 +1,34 @@ 'use strict'; -exports.__esModule = true; +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.ArrayVirtualRepeatStrategy = undefined; -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } +var _arrayRepeatStrategy = require('aurelia-templating-resources/array-repeat-strategy'); -function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } +var _repeatUtilities = require('aurelia-templating-resources/repeat-utilities'); -var _aureliaTemplatingResourcesArrayRepeatStrategy = require('aurelia-templating-resources/array-repeat-strategy'); +var _utilities = require('./utilities'); -var _aureliaTemplatingResourcesRepeatUtilities = require('aurelia-templating-resources/repeat-utilities'); +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } -var ArrayVirtualRepeatStrategy = (function (_ArrayRepeatStrategy) { +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var ArrayVirtualRepeatStrategy = exports.ArrayVirtualRepeatStrategy = function (_ArrayRepeatStrategy) { _inherits(ArrayVirtualRepeatStrategy, _ArrayRepeatStrategy); function ArrayVirtualRepeatStrategy() { _classCallCheck(this, ArrayVirtualRepeatStrategy); - _ArrayRepeatStrategy.apply(this, arguments); + return _possibleConstructorReturn(this, _ArrayRepeatStrategy.apply(this, arguments)); } ArrayVirtualRepeatStrategy.prototype.createFirstItem = function createFirstItem(repeat) { - var overrideContext = _aureliaTemplatingResourcesRepeatUtilities.createFullOverrideContext(repeat, repeat.items[0], 0, 1); - var view = repeat.viewFactory.create(); - view.bind(overrideContext.bindingContext, overrideContext); - repeat.viewSlot.add(view); + var overrideContext = (0, _repeatUtilities.createFullOverrideContext)(repeat, repeat.items[0], 0, 1); + repeat.addView(overrideContext.bindingContext, overrideContext); }; ArrayVirtualRepeatStrategy.prototype.instanceChanged = function instanceChanged(repeat, items) { @@ -32,27 +37,25 @@ var ArrayVirtualRepeatStrategy = (function (_ArrayRepeatStrategy) { ArrayVirtualRepeatStrategy.prototype._standardProcessInstanceChanged = function _standardProcessInstanceChanged(repeat, items) { for (var i = 1, ii = repeat._viewsLength; i < ii; ++i) { - var overrideContext = _aureliaTemplatingResourcesRepeatUtilities.createFullOverrideContext(repeat, items[i], i, ii); - var view = repeat.viewFactory.create(); - view.bind(overrideContext.bindingContext, overrideContext); - repeat.viewSlot.add(view); + var overrideContext = (0, _repeatUtilities.createFullOverrideContext)(repeat, items[i], i, ii); + repeat.addView(overrideContext.bindingContext, overrideContext); } }; ArrayVirtualRepeatStrategy.prototype._inPlaceProcessItems = function _inPlaceProcessItems(repeat, items) { var itemsLength = items.length; - var viewsLength = repeat.viewSlot.children.length; + var viewsLength = repeat.viewCount(); var first = repeat._getIndexOfFirstView(); while (viewsLength > repeat._viewsLength) { viewsLength--; - repeat.viewSlot.removeAt(viewsLength, true); + repeat.removeView(viewsLength, true); } var local = repeat.local; for (var i = 0; i < viewsLength; i++) { - var view = repeat.viewSlot.children[i]; + var view = repeat.view(i); var last = i === itemsLength - 1; var middle = i !== 0 && !last; @@ -63,165 +66,192 @@ var ArrayVirtualRepeatStrategy = (function (_ArrayRepeatStrategy) { view.bindingContext[local] = items[i + first]; view.overrideContext.$middle = middle; view.overrideContext.$last = last; - var j = view.bindings.length; - while (j--) { - _aureliaTemplatingResourcesRepeatUtilities.updateOneTimeBinding(view.bindings[j]); - } - j = view.controllers.length; - while (j--) { - var k = view.controllers[j].boundProperties.length; - while (k--) { - var binding = view.controllers[j].boundProperties[k].binding; - _aureliaTemplatingResourcesRepeatUtilities.updateOneTimeBinding(binding); - } - } + repeat.updateBindings(view); } - for (var i = viewsLength; i < repeat._viewsLength; i++) { - var overrideContext = _aureliaTemplatingResourcesRepeatUtilities.createFullOverrideContext(repeat, items[i], i, itemsLength); - var view = repeat.viewFactory.create(); - view.bind(overrideContext.bindingContext, overrideContext); - repeat.viewSlot.add(view); + var minLength = Math.min(repeat._viewsLength, items.length); + for (var _i = viewsLength; _i < minLength; _i++) { + var overrideContext = (0, _repeatUtilities.createFullOverrideContext)(repeat, items[_i], _i, itemsLength); + repeat.addView(overrideContext.bindingContext, overrideContext); } }; ArrayVirtualRepeatStrategy.prototype.instanceMutated = function instanceMutated(repeat, array, splices) { - this._updateViews(repeat, repeat.items, splices); + this._standardProcessInstanceMutated(repeat, array, splices); }; ArrayVirtualRepeatStrategy.prototype._standardProcessInstanceMutated = function _standardProcessInstanceMutated(repeat, array, splices) { - var _this = this; + var _this2 = this; + + if (repeat.__queuedSplices) { + for (var i = 0, ii = splices.length; i < ii; ++i) { + var _splices$i = splices[i]; + var index = _splices$i.index; + var removed = _splices$i.removed; + var addedCount = _splices$i.addedCount; + + mergeSplice(repeat.__queuedSplices, index, removed, addedCount); + } + repeat.__array = array.slice(0); + return; + } + + var maybePromise = this._runSplices(repeat, array.slice(0), splices); + if (maybePromise instanceof Promise) { + (function () { + var queuedSplices = repeat.__queuedSplices = []; + + var runQueuedSplices = function runQueuedSplices() { + if (!queuedSplices.length) { + delete repeat.__queuedSplices; + delete repeat.__array; + return; + } + + var nextPromise = _this2._runSplices(repeat, repeat.__array, queuedSplices) || Promise.resolve(); + nextPromise.then(runQueuedSplices); + }; + + maybePromise.then(runQueuedSplices); + })(); + } + }; + + ArrayVirtualRepeatStrategy.prototype._runSplices = function _runSplices(repeat, array, splices) { + var _this3 = this; var removeDelta = 0; - var viewSlot = repeat.viewSlot; var rmPromises = []; for (var i = 0, ii = splices.length; i < ii; ++i) { var splice = splices[i]; var removed = splice.removed; - var viewIndex = this._getViewIndex(repeat, viewSlot, splice.index); - if (viewIndex >= 0) { - for (var j = 0, jj = removed.length; j < jj; ++j) { - var viewOrPromise = viewSlot.removeAt(viewIndex + removeDelta + rmPromises.length, true); - - var _length = viewSlot.children.length; - var overrideContext = _aureliaTemplatingResourcesRepeatUtilities.createFullOverrideContext(repeat, repeat.items[_length], _length, repeat.items.length); - var view = repeat.viewFactory.create(); - view.bind(overrideContext.bindingContext, overrideContext); - repeat.viewSlot.isAttached = false; - repeat.viewSlot.add(view); - repeat.viewSlot.isAttached = true; - - if (viewOrPromise instanceof Promise) { - rmPromises.push(viewOrPromise); - } + for (var j = 0, jj = removed.length; j < jj; ++j) { + var viewOrPromise = this._removeViewAt(repeat, splice.index + removeDelta + rmPromises.length, true); + if (viewOrPromise instanceof Promise) { + rmPromises.push(viewOrPromise); } - removeDelta -= splice.addedCount; } + removeDelta -= splice.addedCount; } if (rmPromises.length > 0) { - Promise.all(rmPromises).then(function () { - _this._handleAddedSplices(repeat, array, splices); - _this._updateViews(repeat, array, splices); + return Promise.all(rmPromises).then(function () { + _this3._handleAddedSplices(repeat, array, splices); + (0, _utilities.updateVirtualOverrideContexts)(repeat, 0); }); - } else { - this._handleAddedSplices(repeat, array, splices); - this._updateViews(repeat, array, splices); } + this._handleAddedSplices(repeat, array, splices); + (0, _utilities.updateVirtualOverrideContexts)(repeat, 0); }; - ArrayVirtualRepeatStrategy.prototype._updateViews = function _updateViews(repeat, items, splices) { - var totalAdded = 0; - var totalRemoved = 0; - repeat.items = items; - - for (var i = 0, ii = splices.length; i < ii; ++i) { - var splice = splices[0]; - totalAdded += splice.addedCount; - totalRemoved += splice.removed.length; + ArrayVirtualRepeatStrategy.prototype._removeViewAt = function _removeViewAt(repeat, collectionIndex, returnToCache) { + var viewOrPromise = void 0; + var view = void 0; + var viewSlot = repeat.viewSlot; + var viewCount = repeat.viewCount(); + var viewAddIndex = void 0; + + if (!this._isIndexBeforeViewSlot(repeat, viewSlot, collectionIndex) && !this._isIndexAfterViewSlot(repeat, viewSlot, collectionIndex)) { + var viewIndex = this._getViewIndex(repeat, viewSlot, collectionIndex); + viewOrPromise = repeat.removeView(viewIndex, returnToCache); + if (repeat.items.length > viewCount) { + var collectionAddIndex = void 0; + if (repeat._bottomBufferHeight > repeat.itemHeight) { + viewAddIndex = viewCount; + collectionAddIndex = repeat._getIndexOfLastView() + 1; + repeat._bottomBufferHeight = repeat._bottomBufferHeight - repeat.itemHeight; + } else if (repeat._topBufferHeight > 0) { + viewAddIndex = 0; + collectionAddIndex = repeat._getIndexOfFirstView() - 1; + repeat._topBufferHeight = repeat._topBufferHeight - repeat.itemHeight; + } + var data = repeat.items[collectionAddIndex]; + if (data) { + var overrideContext = (0, _repeatUtilities.createFullOverrideContext)(repeat, repeat.items[collectionAddIndex], collectionAddIndex, repeat.items.length); + view = repeat.viewFactory.create(); + view.bind(overrideContext.bindingContext, overrideContext); + } + } else { + return viewOrPromise; + } + } else if (this._isIndexBeforeViewSlot(repeat, viewSlot, collectionIndex)) { + if (repeat._bottomBufferHeight > 0) { + repeat._bottomBufferHeight = repeat._bottomBufferHeight - repeat.itemHeight; + (0, _utilities.rebindAndMoveView)(repeat, repeat.view(0), repeat.view(0).overrideContext.$index, true); + } else { + repeat._topBufferHeight = repeat._topBufferHeight - repeat.itemHeight; + } + } else if (this._isIndexAfterViewSlot(repeat, viewSlot, collectionIndex)) { + repeat._bottomBufferHeight = repeat._bottomBufferHeight - repeat.itemHeight; } - var index = repeat._getIndexOfFirstView() - totalRemoved; - - if (index < 0) { - index = 0; + if (viewOrPromise instanceof Promise) { + viewOrPromise.then(function () { + repeat.viewSlot.insert(viewAddIndex, view); + repeat._adjustBufferHeights(); + }); + return undefined; + } else if (view) { + repeat.viewSlot.insert(viewAddIndex, view); } - var viewSlot = repeat.viewSlot; - - for (var i = 0, ii = viewSlot.children.length; i < ii; ++i) { - var view = viewSlot.children[i]; - var nextIndex = index + i; - var itemsLength = items.length; - if (nextIndex <= itemsLength - 1) { - view.bindingContext[repeat.local] = items[nextIndex]; - _aureliaTemplatingResourcesRepeatUtilities.updateOverrideContext(view.overrideContext, nextIndex, itemsLength); - } - } + repeat._adjustBufferHeights(); + }; - var bufferDelta = repeat.itemHeight * totalAdded + repeat.itemHeight * -totalRemoved; + ArrayVirtualRepeatStrategy.prototype._isIndexBeforeViewSlot = function _isIndexBeforeViewSlot(repeat, viewSlot, index) { + var viewIndex = this._getViewIndex(repeat, viewSlot, index); + return viewIndex < 0; + }; - if (repeat._bottomBufferHeight + bufferDelta < 0) { - repeat._topBufferHeight = repeat._topBufferHeight + bufferDelta; - } else { - repeat._bottomBufferHeight = repeat._bottomBufferHeight + bufferDelta; - } + ArrayVirtualRepeatStrategy.prototype._isIndexAfterViewSlot = function _isIndexAfterViewSlot(repeat, viewSlot, index) { + var viewIndex = this._getViewIndex(repeat, viewSlot, index); + return viewIndex > repeat._viewsLength - 1; + }; - if (repeat._bottomBufferHeight > 0) { - repeat.isLastIndex = false; + ArrayVirtualRepeatStrategy.prototype._getViewIndex = function _getViewIndex(repeat, viewSlot, index) { + if (repeat.viewCount() === 0) { + return -1; } - repeat._adjustBufferHeights(); + var topBufferItems = repeat._topBufferHeight / repeat.itemHeight; + return index - topBufferItems; }; ArrayVirtualRepeatStrategy.prototype._handleAddedSplices = function _handleAddedSplices(repeat, array, splices) { - var spliceIndexLow = undefined; var arrayLength = array.length; + var viewSlot = repeat.viewSlot; for (var i = 0, ii = splices.length; i < ii; ++i) { var splice = splices[i]; var addIndex = splice.index; var end = splice.index + splice.addedCount; - - if (typeof spliceIndexLow === 'undefined' || spliceIndexLow === null || spliceIndexLow > splice.index) { - spliceIndexLow = addIndex; - } - for (; addIndex < end; ++addIndex) { - var overrideContext = _aureliaTemplatingResourcesRepeatUtilities.createFullOverrideContext(repeat, array[addIndex], addIndex, arrayLength); - var view = repeat.viewFactory.create(); - view.bind(overrideContext.bindingContext, overrideContext); - repeat.viewSlot.insert(addIndex, view); + var hasDistanceToBottomViewPort = (0, _utilities.getElementDistanceToBottomViewPort)(repeat.bottomBuffer.previousElementSibling) > 0; + if (repeat.viewCount() === 0 || !this._isIndexBeforeViewSlot(repeat, viewSlot, addIndex) && !this._isIndexAfterViewSlot(repeat, viewSlot, addIndex) || hasDistanceToBottomViewPort) { + var overrideContext = (0, _repeatUtilities.createFullOverrideContext)(repeat, array[addIndex], addIndex, arrayLength); + repeat.insertView(addIndex, overrideContext.bindingContext, overrideContext); + if (!repeat._hasCalculatedSizes) { + repeat._calcInitialHeights(1); + } else if (repeat.viewCount() > repeat._viewsLength) { + if (hasDistanceToBottomViewPort) { + repeat.removeView(0, true, true); + repeat._topBufferHeight = repeat._topBufferHeight + repeat.itemHeight; + repeat._adjustBufferHeights(); + } else { + repeat.removeView(repeat.viewCount() - 1, true, true); + repeat._bottomBufferHeight = repeat._bottomBufferHeight + repeat.itemHeight; + } + } + } else if (this._isIndexBeforeViewSlot(repeat, viewSlot, addIndex)) { + repeat._topBufferHeight = repeat._topBufferHeight + repeat.itemHeight; + } else if (this._isIndexAfterViewSlot(repeat, viewSlot, addIndex)) { + repeat._bottomBufferHeight = repeat._bottomBufferHeight + repeat.itemHeight; + repeat.isLastIndex = false; + } } } - - return spliceIndexLow; - }; - - ArrayVirtualRepeatStrategy.prototype._isIndexInDom = function _isIndexInDom(viewSlot, index) { - if (viewSlot.children.length === 0) { - return false; - } - - var indexLow = viewSlot.children[0].overrideContext.$index; - var indexHi = viewSlot.children[viewSlot.children.length - 1].overrideContext.$index; - - return index >= indexLow && index <= indexHi; - }; - - ArrayVirtualRepeatStrategy.prototype._getViewIndex = function _getViewIndex(repeat, viewSlot, index) { - if (viewSlot.children.length === 0) { - return -1; - } - var indexLow = viewSlot.children[0].overrideContext.$index; - var viewIndex = index - indexLow; - if (viewIndex > repeat._viewsLength - 1) { - viewIndex = -1; - } - return viewIndex; + repeat._adjustBufferHeights(); }; return ArrayVirtualRepeatStrategy; -})(_aureliaTemplatingResourcesArrayRepeatStrategy.ArrayRepeatStrategy); - -exports.ArrayVirtualRepeatStrategy = ArrayVirtualRepeatStrategy; \ No newline at end of file +}(_arrayRepeatStrategy.ArrayRepeatStrategy); \ No newline at end of file diff --git a/dist/commonjs/aurelia-ui-virtualization.d.ts b/dist/commonjs/aurelia-ui-virtualization.d.ts new file mode 100644 index 0000000..cc50ca3 --- /dev/null +++ b/dist/commonjs/aurelia-ui-virtualization.d.ts @@ -0,0 +1,131 @@ +declare module 'aurelia-ui-virtualization' { + import { + updateOverrideContext, + createFullOverrideContext, + getItemsSourceExpression, + isOneTime, + unwrapExpression, + updateOneTimeBinding + } from 'aurelia-templating-resources/repeat-utilities'; + import { + ArrayRepeatStrategy + } from 'aurelia-templating-resources/array-repeat-strategy'; + import { + RepeatStrategyLocator + } from 'aurelia-templating-resources/repeat-strategy-locator'; + import { + inject + } from 'aurelia-dependency-injection'; + import { + ObserverLocator + } from 'aurelia-binding'; + import { + BoundViewFactory, + ViewSlot, + TargetInstruction, + customAttribute, + bindable, + templateController + } from 'aurelia-templating'; + import { + AbstractRepeater + } from 'aurelia-templating-resources'; + import { + viewsRequireLifecycle + } from 'aurelia-templating-resources/analyze-view-factory'; + export function calcOuterHeight(element: any): any; + export function insertBeforeNode(view: any, bottomBuffer: any): any; + + /** + * Update the override context. + * @param startIndex index in collection where to start updating. + */ + export function updateVirtualOverrideContexts(repeat: any, startIndex: any): any; + export function rebindAndMoveView(repeat: VirtualRepeat, view: View, index: number, moveToBottom: boolean): void; + export function getStyleValue(element: any, style: any): any; + export function getElementDistanceToBottomViewPort(element: any): any; + + /** + * A strategy for repeating a template over an array. + */ + export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy { + + // create first item to calculate the heights + createFirstItem(repeat: any): any; + + /** + * Handle the repeat's collection instance changing. + * @param repeat The repeater instance. + * @param items The new array instance. + */ + instanceChanged(repeat: any, items: any): any; + + /** + * Handle the repeat's collection instance mutating. + * @param repeat The repeat instance. + * @param array The modified array. + * @param splices Records of array changes. + */ + instanceMutated(repeat: any, array: any, splices: any): any; + } + export class ViewStrategyLocator { + getStrategy(element: any): any; + } + export class TableStrategy { + getScrollContainer(element: any): any; + moveViewFirst(view: any, topBuffer: any): any; + moveViewLast(view: any, bottomBuffer: any): any; + createTopBufferElement(element: any): any; + createBottomBufferElement(element: any): any; + removeBufferElements(element: any, topBuffer: any, bottomBuffer: any): any; + } + export class DefaultStrategy { + getScrollContainer(element: any): any; + moveViewFirst(view: any, topBuffer: any): any; + moveViewLast(view: any, bottomBuffer: any): any; + createTopBufferElement(element: any): any; + createBottomBufferElement(element: any): any; + removeBufferElements(element: any, topBuffer: any, bottomBuffer: any): any; + } + export class VirtualRepeatStrategyLocator extends RepeatStrategyLocator { + constructor(); + } + export class VirtualRepeat extends AbstractRepeater { + _first: any; + _previousFirst: any; + _viewsLength: any; + _lastRebind: any; + _topBufferHeight: any; + _bottomBufferHeight: any; + _bufferSize: any; + _scrollingDown: any; + _scrollingUp: any; + _switchedDirection: any; + _isAttached: any; + _ticking: any; + _fixedHeightContainer: any; + _hasCalculatedSizes: any; + _isAtTop: any; + items: any; + local: any; + constructor(element: any, viewFactory: any, instruction: any, viewSlot: any, observerLocator: any, strategyLocator: any, viewStrategyLocator: any); + attached(): any; + bind(bindingContext: any, overrideContext: any): any; + call(context: any, changes: any): any; + detached(): any; + itemsChanged(): any; + unbind(): any; + handleCollectionMutated(collection: any, changes: any): any; + handleInnerCollectionMutated(collection: any, changes: any): any; + + // @override AbstractRepeater + viewCount(): any; + views(): any; + view(index: any): any; + addView(bindingContext: any, overrideContext: any): any; + insertView(index: any, bindingContext: any, overrideContext: any): any; + removeAllViews(returnToCache: any, skipAnimation: any): any; + removeView(index: any, returnToCache: any, skipAnimation: any): any; + updateBindings(view: View): any; + } +} \ No newline at end of file diff --git a/dist/commonjs/index.js b/dist/commonjs/index.js index cf41e2a..fae8111 100644 --- a/dist/commonjs/index.js +++ b/dist/commonjs/index.js @@ -1,15 +1,15 @@ 'use strict'; -exports.__esModule = true; +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.VirtualRepeat = undefined; exports.configure = configure; var _virtualRepeat = require('./virtual-repeat'); -var _virtualList = require('./virtual-list'); - function configure(config) { - config.globalResources('./virtual-repeat', './virtual-list'); + config.globalResources('./virtual-repeat'); } -exports.VirtualRepeat = _virtualRepeat.VirtualRepeat; -exports.VirtualList = _virtualList.VirtualList; \ No newline at end of file +exports.VirtualRepeat = _virtualRepeat.VirtualRepeat; \ No newline at end of file diff --git a/dist/commonjs/utilities.js b/dist/commonjs/utilities.js index 660edb7..130ce49 100644 --- a/dist/commonjs/utilities.js +++ b/dist/commonjs/utilities.js @@ -1,39 +1,74 @@ 'use strict'; -exports.__esModule = true; +Object.defineProperty(exports, "__esModule", { + value: true +}); exports.calcOuterHeight = calcOuterHeight; -exports.calcScrollHeight = calcScrollHeight; exports.insertBeforeNode = insertBeforeNode; +exports.updateVirtualOverrideContexts = updateVirtualOverrideContexts; +exports.rebindAndMoveView = rebindAndMoveView; +exports.getStyleValue = getStyleValue; +exports.getElementDistanceToBottomViewPort = getElementDistanceToBottomViewPort; + +var _repeatUtilities = require('aurelia-templating-resources/repeat-utilities'); function calcOuterHeight(element) { - var height; + var height = void 0; height = element.getBoundingClientRect().height; height += getStyleValue(element, 'marginTop'); height += getStyleValue(element, 'marginBottom'); return height; } -function calcScrollHeight(element) { - var height; - height = element.getBoundingClientRect().height; - height -= getStyleValue(element, 'borderTopWidth'); - height -= getStyleValue(element, 'borderBottomWidth'); - return height; -} - -function insertBeforeNode(view, scrollView, node) { +function insertBeforeNode(view, bottomBuffer) { var viewStart = view.firstChild; var element = viewStart.nextSibling; var viewEnd = view.lastChild; + var parentElement = bottomBuffer.parentElement; + + parentElement.insertBefore(viewEnd, bottomBuffer); + parentElement.insertBefore(element, viewEnd); + parentElement.insertBefore(viewStart, element); +} - scrollView.insertBefore(viewEnd, node); - scrollView.insertBefore(element, viewEnd); - scrollView.insertBefore(viewStart, element); +function updateVirtualOverrideContexts(repeat, startIndex) { + var views = repeat.viewSlot.children; + var viewLength = views.length; + var collectionLength = repeat.items.length; + + if (startIndex > 0) { + startIndex = startIndex - 1; + } + + var delta = repeat._topBufferHeight / repeat.itemHeight; + + for (; startIndex < viewLength; ++startIndex) { + (0, _repeatUtilities.updateOverrideContext)(views[startIndex].overrideContext, startIndex + delta, collectionLength); + } +} + +function rebindAndMoveView(repeat, view, index, moveToBottom) { + var items = repeat.items; + var viewSlot = repeat.viewSlot; + (0, _repeatUtilities.updateOverrideContext)(view.overrideContext, index, items.length); + view.bindingContext[repeat.local] = items[index]; + if (moveToBottom) { + viewSlot.children.push(viewSlot.children.shift()); + repeat.viewStrategy.moveViewLast(view, repeat.bottomBuffer); + } else { + viewSlot.children.unshift(viewSlot.children.splice(-1, 1)[0]); + repeat.viewStrategy.moveViewFirst(view, repeat.topBuffer); + } } function getStyleValue(element, style) { - var currentStyle, styleValue; + var currentStyle = void 0; + var styleValue = void 0; currentStyle = element.currentStyle || window.getComputedStyle(element); - styleValue = parseInt(currentStyle[style]); + styleValue = parseInt(currentStyle[style], 10); return Number.isNaN(styleValue) ? 0 : styleValue; +} + +function getElementDistanceToBottomViewPort(element) { + return document.documentElement.clientHeight - element.getBoundingClientRect().bottom; } \ No newline at end of file diff --git a/dist/commonjs/view-strategy.js b/dist/commonjs/view-strategy.js index 224dcdb..c0178e7 100644 --- a/dist/commonjs/view-strategy.js +++ b/dist/commonjs/view-strategy.js @@ -59,9 +59,9 @@ var TableStrategy = exports.TableStrategy = function () { return buffer; }; - TableStrategy.prototype.removeBufferElements = function removeBufferElements(scrollList, topBuffer, bottomBuffer) { - scrollList.removeChild(topBuffer.parentElement); - scrollList.removeChild(bottomBuffer.parentElement); + TableStrategy.prototype.removeBufferElements = function removeBufferElements(element, topBuffer, bottomBuffer) { + element.parentElement.removeChild(topBuffer.parentElement); + element.parentElement.removeChild(bottomBuffer.parentElement); }; return TableStrategy; @@ -81,7 +81,9 @@ var DefaultStrategy = exports.DefaultStrategy = function () { }; DefaultStrategy.prototype.moveViewLast = function moveViewLast(view, bottomBuffer) { - (0, _utilities.insertBeforeNode)(view, bottomBuffer); + var previousSibling = bottomBuffer.previousSibling; + var referenceNode = previousSibling.nodeType === 8 && previousSibling.data === 'anchor' ? previousSibling : bottomBuffer; + (0, _utilities.insertBeforeNode)(view, referenceNode); }; DefaultStrategy.prototype.createTopBufferElement = function createTopBufferElement(element) { @@ -101,8 +103,8 @@ var DefaultStrategy = exports.DefaultStrategy = function () { }; DefaultStrategy.prototype.removeBufferElements = function removeBufferElements(element, topBuffer, bottomBuffer) { - element.removeChild(topBuffer); - element.removeChild(bottomBuffer); + element.parentElement.removeChild(topBuffer); + element.parentElement.removeChild(bottomBuffer); }; return DefaultStrategy; diff --git a/dist/commonjs/virtual-list.html b/dist/commonjs/virtual-list.html deleted file mode 100644 index fcba809..0000000 --- a/dist/commonjs/virtual-list.html +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/dist/commonjs/virtual-list.js b/dist/commonjs/virtual-list.js deleted file mode 100644 index db77dc1..0000000 --- a/dist/commonjs/virtual-list.js +++ /dev/null @@ -1,36 +0,0 @@ -'use strict'; - -exports.__esModule = true; - -var _createDecoratedClass = (function () { function defineProperties(target, descriptors, initializers) { for (var i = 0; i < descriptors.length; i++) { var descriptor = descriptors[i]; var decorators = descriptor.decorators; var key = descriptor.key; delete descriptor.key; delete descriptor.decorators; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor || descriptor.initializer) descriptor.writable = true; if (decorators) { for (var f = 0; f < decorators.length; f++) { var decorator = decorators[f]; if (typeof decorator === 'function') { descriptor = decorator(target, key, descriptor) || descriptor; } else { throw new TypeError('The decorator for method ' + descriptor.key + ' is of the invalid type ' + typeof decorator); } } if (descriptor.initializer !== undefined) { initializers[key] = descriptor; continue; } } Object.defineProperty(target, key, descriptor); } } return function (Constructor, protoProps, staticProps, protoInitializers, staticInitializers) { if (protoProps) defineProperties(Constructor.prototype, protoProps, protoInitializers); if (staticProps) defineProperties(Constructor, staticProps, staticInitializers); return Constructor; }; })(); - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - -function _defineDecoratedPropertyDescriptor(target, key, descriptors) { var _descriptor = descriptors[key]; if (!_descriptor) return; var descriptor = {}; for (var _key in _descriptor) descriptor[_key] = _descriptor[_key]; descriptor.value = descriptor.initializer ? descriptor.initializer.call(target) : undefined; Object.defineProperty(target, key, descriptor); } - -var _aureliaTemplating = require('aurelia-templating'); - -var VirtualList = (function () { - var _instanceInitializers = {}; - - function VirtualList() { - _classCallCheck(this, VirtualList); - - _defineDecoratedPropertyDescriptor(this, 'items', _instanceInitializers); - } - - VirtualList.prototype.bind = function bind(bindingContext, overrideContext) { - this.$parent = bindingContext; - }; - - _createDecoratedClass(VirtualList, [{ - key: 'items', - decorators: [_aureliaTemplating.bindable], - initializer: null, - enumerable: true - }], null, _instanceInitializers); - - return VirtualList; -})(); - -exports.VirtualList = VirtualList; \ No newline at end of file diff --git a/dist/commonjs/virtual-repeat-strategy-locator.js b/dist/commonjs/virtual-repeat-strategy-locator.js index 8be9fa3..1d471c6 100644 --- a/dist/commonjs/virtual-repeat-strategy-locator.js +++ b/dist/commonjs/virtual-repeat-strategy-locator.js @@ -1,31 +1,36 @@ 'use strict'; -exports.__esModule = true; +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.VirtualRepeatStrategyLocator = undefined; -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } +var _repeatStrategyLocator = require('aurelia-templating-resources/repeat-strategy-locator'); -function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } +var _arrayVirtualRepeatStrategy = require('./array-virtual-repeat-strategy'); -var _aureliaTemplatingResourcesRepeatStrategyLocator = require('aurelia-templating-resources/repeat-strategy-locator'); +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } -var _arrayVirtualRepeatStrategy = require('./array-virtual-repeat-strategy'); +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } -var VirtualRepeatStrategyLocator = (function (_RepeatStrategyLocator) { - _inherits(VirtualRepeatStrategyLocator, _RepeatStrategyLocator); +var VirtualRepeatStrategyLocator = exports.VirtualRepeatStrategyLocator = function (_RepeatStrategyLocato) { + _inherits(VirtualRepeatStrategyLocator, _RepeatStrategyLocato); function VirtualRepeatStrategyLocator() { _classCallCheck(this, VirtualRepeatStrategyLocator); - _RepeatStrategyLocator.call(this); - this.matchers = []; - this.strategies = []; + var _this = _possibleConstructorReturn(this, _RepeatStrategyLocato.call(this)); - this.addStrategy(function (items) { + _this.matchers = []; + _this.strategies = []; + + _this.addStrategy(function (items) { return items instanceof Array; }, new _arrayVirtualRepeatStrategy.ArrayVirtualRepeatStrategy()); + return _this; } return VirtualRepeatStrategyLocator; -})(_aureliaTemplatingResourcesRepeatStrategyLocator.RepeatStrategyLocator); - -exports.VirtualRepeatStrategyLocator = VirtualRepeatStrategyLocator; \ No newline at end of file +}(_repeatStrategyLocator.RepeatStrategyLocator); \ No newline at end of file diff --git a/dist/commonjs/virtual-repeat.js b/dist/commonjs/virtual-repeat.js index dc7c40d..3ebdf9e 100644 --- a/dist/commonjs/virtual-repeat.js +++ b/dist/commonjs/virtual-repeat.js @@ -1,12 +1,11 @@ 'use strict'; -exports.__esModule = true; +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.VirtualRepeat = undefined; -var _createDecoratedClass = (function () { function defineProperties(target, descriptors, initializers) { for (var i = 0; i < descriptors.length; i++) { var descriptor = descriptors[i]; var decorators = descriptor.decorators; var key = descriptor.key; delete descriptor.key; delete descriptor.decorators; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor || descriptor.initializer) descriptor.writable = true; if (decorators) { for (var f = 0; f < decorators.length; f++) { var decorator = decorators[f]; if (typeof decorator === 'function') { descriptor = decorator(target, key, descriptor) || descriptor; } else { throw new TypeError('The decorator for method ' + descriptor.key + ' is of the invalid type ' + typeof decorator); } } if (descriptor.initializer !== undefined) { initializers[key] = descriptor; continue; } } Object.defineProperty(target, key, descriptor); } } return function (Constructor, protoProps, staticProps, protoInitializers, staticInitializers) { if (protoProps) defineProperties(Constructor.prototype, protoProps, protoInitializers); if (staticProps) defineProperties(Constructor, staticProps, staticInitializers); return Constructor; }; })(); - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - -function _defineDecoratedPropertyDescriptor(target, key, descriptors) { var _descriptor = descriptors[key]; if (!_descriptor) return; var descriptor = {}; for (var _key in _descriptor) descriptor[_key] = _descriptor[_key]; descriptor.value = descriptor.initializer ? descriptor.initializer.call(target) : undefined; Object.defineProperty(target, key, descriptor); } +var _dec, _dec2, _class, _desc, _value, _class2, _descriptor, _descriptor2; var _aureliaDependencyInjection = require('aurelia-dependency-injection'); @@ -14,9 +13,11 @@ var _aureliaBinding = require('aurelia-binding'); var _aureliaTemplating = require('aurelia-templating'); -var _aureliaTemplatingResourcesRepeatUtilities = require('aurelia-templating-resources/repeat-utilities'); +var _aureliaTemplatingResources = require('aurelia-templating-resources'); + +var _repeatUtilities = require('aurelia-templating-resources/repeat-utilities'); -var _aureliaTemplatingResourcesAnalyzeViewFactory = require('aurelia-templating-resources/analyze-view-factory'); +var _analyzeViewFactory = require('aurelia-templating-resources/analyze-view-factory'); var _utilities = require('./utilities'); @@ -24,80 +25,123 @@ var _virtualRepeatStrategyLocator = require('./virtual-repeat-strategy-locator') var _viewStrategy = require('./view-strategy'); -var VirtualRepeat = (function () { - var _instanceInitializers = {}; - - _createDecoratedClass(VirtualRepeat, [{ - key: 'items', - decorators: [_aureliaTemplating.bindable], - initializer: null, - enumerable: true - }, { - key: 'local', - decorators: [_aureliaTemplating.bindable], - initializer: null, - enumerable: true - }], null, _instanceInitializers); +function _initDefineProp(target, property, descriptor, context) { + if (!descriptor) return; + Object.defineProperty(target, property, { + enumerable: descriptor.enumerable, + configurable: descriptor.configurable, + writable: descriptor.writable, + value: descriptor.initializer ? descriptor.initializer.call(context) : void 0 + }); +} - function VirtualRepeat(element, viewFactory, instruction, viewSlot, observerLocator, strategyLocator, viewStrategyLocator) { - _classCallCheck(this, _VirtualRepeat); +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - this._first = 0; - this._previousFirst = 0; - this._viewsLength = 0; - this._lastRebind = 0; - this._topBufferHeight = 0; - this._bottomBufferHeight = 0; - this._bufferSize = 5; - this._scrollingDown = false; - this._scrollingUp = false; - this._switchedDirection = false; - this._isAttached = false; - this._ticking = false; +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } - _defineDecoratedPropertyDescriptor(this, 'items', _instanceInitializers); - - _defineDecoratedPropertyDescriptor(this, 'local', _instanceInitializers); - - this.element = element; - this.viewFactory = viewFactory; - this.instruction = instruction; - this.viewSlot = viewSlot; - this.observerLocator = observerLocator; - this.strategyLocator = strategyLocator; - this.viewStrategyLocator = viewStrategyLocator; - this.local = 'item'; - this.sourceExpression = _aureliaTemplatingResourcesRepeatUtilities.getItemsSourceExpression(this.instruction, 'virtual-repeat.for'); - this.isOneTime = _aureliaTemplatingResourcesRepeatUtilities.isOneTime(this.sourceExpression); - this.viewsRequireLifecycle = _aureliaTemplatingResourcesAnalyzeViewFactory.viewsRequireLifecycle(viewFactory); + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; +} + +function _initializerWarningHelper(descriptor, context) { + throw new Error('Decorating class property failed. Please ensure that transform-class-properties is enabled.'); +} + +var VirtualRepeat = exports.VirtualRepeat = (_dec = (0, _aureliaTemplating.customAttribute)('virtual-repeat'), _dec2 = (0, _aureliaDependencyInjection.inject)(Element, _aureliaTemplating.BoundViewFactory, _aureliaTemplating.TargetInstruction, _aureliaTemplating.ViewSlot, _aureliaBinding.ObserverLocator, _virtualRepeatStrategyLocator.VirtualRepeatStrategyLocator, _viewStrategy.ViewStrategyLocator), _dec(_class = (0, _aureliaTemplating.templateController)(_class = _dec2(_class = (_class2 = function (_AbstractRepeater) { + _inherits(VirtualRepeat, _AbstractRepeater); + + function VirtualRepeat(element, viewFactory, instruction, viewSlot, observerLocator, strategyLocator, viewStrategyLocator) { + _classCallCheck(this, VirtualRepeat); + + var _this = _possibleConstructorReturn(this, _AbstractRepeater.call(this, { + local: 'item', + viewsRequireLifecycle: (0, _analyzeViewFactory.viewsRequireLifecycle)(viewFactory) + })); + + _this._first = 0; + _this._previousFirst = 0; + _this._viewsLength = 0; + _this._lastRebind = 0; + _this._topBufferHeight = 0; + _this._bottomBufferHeight = 0; + _this._bufferSize = 5; + _this._scrollingDown = false; + _this._scrollingUp = false; + _this._switchedDirection = false; + _this._isAttached = false; + _this._ticking = false; + _this._fixedHeightContainer = false; + _this._hasCalculatedSizes = false; + _this._isAtTop = true; + + _initDefineProp(_this, 'items', _descriptor, _this); + + _initDefineProp(_this, 'local', _descriptor2, _this); + + _this.element = element; + _this.viewFactory = viewFactory; + _this.instruction = instruction; + _this.viewSlot = viewSlot; + _this.observerLocator = observerLocator; + _this.strategyLocator = strategyLocator; + _this.viewStrategyLocator = viewStrategyLocator; + _this.sourceExpression = (0, _repeatUtilities.getItemsSourceExpression)(_this.instruction, 'virtual-repeat.for'); + _this.isOneTime = (0, _repeatUtilities.isOneTime)(_this.sourceExpression); + return _this; } VirtualRepeat.prototype.attached = function attached() { - var _this = this; + var _this2 = this; this._isAttached = true; var element = this.element; this.viewStrategy = this.viewStrategyLocator.getStrategy(element); - this.scrollList = this.viewStrategy.getScrollList(element); this.scrollContainer = this.viewStrategy.getScrollContainer(element); - this.topBuffer = this.viewStrategy.createTopBufferElement(this.scrollList, element); - this.bottomBuffer = this.viewStrategy.createBottomBufferElement(this.scrollList, element); + this.topBuffer = this.viewStrategy.createTopBufferElement(element); + this.bottomBuffer = this.viewStrategy.createBottomBufferElement(element); this.itemsChanged(); this.scrollListener = function () { - return _this._onScroll(); + return _this2._onScroll(); }; - this.scrollContainer.addEventListener('scroll', this.scrollListener); + var containerStyle = this.scrollContainer.style; + if (containerStyle.overflowY === 'scroll' || containerStyle.overflow === 'scroll' || containerStyle.overflowY === 'auto' || containerStyle.overflow === 'auto') { + this._fixedHeightContainer = true; + this.scrollContainer.addEventListener('scroll', this.scrollListener); + } else { + document.addEventListener('scroll', this.scrollListener); + } }; VirtualRepeat.prototype.bind = function bind(bindingContext, overrideContext) { - var _this2 = this; - this.scope = { bindingContext: bindingContext, overrideContext: overrideContext }; this._itemsLength = this.items.length; - - window.onresize = function () { - _this2._handleResize(); - }; }; VirtualRepeat.prototype.call = function call(context, changes) { @@ -117,12 +161,12 @@ var VirtualRepeat = (function () { this._switchedDirection = false; this._isAttached = false; this._ticking = false; - this.viewStrategy.removeBufferElements(this.scrollList, this.topBuffer, this.bottomBuffer); + this._hasCalculatedSizes = false; + this.viewStrategy.removeBufferElements(this.element, this.topBuffer, this.bottomBuffer); this.isLastIndex = false; - this.scrollList = null; this.scrollContainer = null; this.scrollContainerHeight = null; - this.viewSlot.removeAll(true); + this.removeAllViews(true); if (this.scrollHandler) { this.scrollHandler.dispose(); } @@ -137,8 +181,10 @@ var VirtualRepeat = (function () { } var items = this.items; this.strategy = this.strategyLocator.getStrategy(items); - this.strategy.createFirstItem(this); - this._calcInitialHeights(); + if (items.length > 0) { + this.strategy.createFirstItem(this); + } + this._calcInitialHeights(items.length); if (!this.isOneTime && !this._observeInnerCollection()) { this._observeCollection(); } @@ -153,6 +199,7 @@ var VirtualRepeat = (function () { }; VirtualRepeat.prototype.handleCollectionMutated = function handleCollectionMutated(collection, changes) { + this._handlingMutations = true; this._itemsLength = collection.length; this.strategy.instanceMutated(this, collection, changes); }; @@ -179,12 +226,16 @@ var VirtualRepeat = (function () { VirtualRepeat.prototype._onScroll = function _onScroll() { var _this4 = this; - if (!this._ticking) { + if (!this._ticking && !this._handlingMutations) { requestAnimationFrame(function () { return _this4._handleScroll(); }); this._ticking = true; } + + if (this._handlingMutations) { + this._handlingMutations = false; + } }; VirtualRepeat.prototype._handleScroll = function _handleScroll() { @@ -192,73 +243,55 @@ var VirtualRepeat = (function () { return; } var itemHeight = this.itemHeight; - var scrollTop = this.scrollContainer.scrollTop; + var scrollTop = this._fixedHeightContainer ? this.scrollContainer.scrollTop : pageYOffset - this.topBuffer.offsetTop; this._first = Math.floor(scrollTop / itemHeight); + this._first = this._first < 0 ? 0 : this._first; + if (this._first > this.items.length - this.elementsInView) { + this._first = this.items.length - this.elementsInView; + } this._checkScrolling(); - if (this._scrollingDown && (this._hasScrolledDownTheBuffer() || this._switchedDirection && this._hasScrolledDownTheBufferFromTop())) { + if (this._scrollingDown) { var viewsToMove = this._first - this._lastRebind; if (this._switchedDirection) { - viewsToMove = this.isAtTop ? this._first : this._bufferSize - (this._lastRebind - this._first); + viewsToMove = this._isAtTop ? this._first : this._bufferSize - (this._lastRebind - this._first); } - this.isAtTop = false; + this._isAtTop = false; this._lastRebind = this._first; var movedViewsCount = this._moveViews(viewsToMove); var adjustHeight = movedViewsCount < viewsToMove ? this._bottomBufferHeight : itemHeight * movedViewsCount; - var test = 0; this._switchedDirection = false; this._topBufferHeight = this._topBufferHeight + adjustHeight; this._bottomBufferHeight = this._bottomBufferHeight - adjustHeight; if (this._bottomBufferHeight >= 0) { this._adjustBufferHeights(); } - } else if (this._scrollingUp && (this._hasScrolledUpTheBuffer() || this._switchedDirection && this._hasScrolledUpTheBufferFromBottom())) { - var viewsToMove = this._lastRebind - this._first; - if (this._switchedDirection) { - if (this.isLastIndex) { - viewsToMove = this.items.length - this._first - this.elementsInView; - } else { - viewsToMove = this._bufferSize - (this._first - this._lastRebind); - } - } - this.isLastIndex = false; - this._lastRebind = this._first; - var movedViewsCount = this._moveViews(viewsToMove); - this.movedViewsCount = movedViewsCount; - var adjustHeight = movedViewsCount < viewsToMove ? this._topBufferHeight : itemHeight * movedViewsCount; - this._switchedDirection = false; - this._topBufferHeight = this._topBufferHeight - adjustHeight; - this._bottomBufferHeight = this._bottomBufferHeight + adjustHeight; - if (this._topBufferHeight >= 0) { - this._adjustBufferHeights(); + } else if (this._scrollingUp) { + var _viewsToMove = this._lastRebind - this._first; + if (this._switchedDirection) { + if (this.isLastIndex) { + _viewsToMove = this.items.length - this._first - this.elementsInView; + } else { + _viewsToMove = this._bufferSize - (this._first - this._lastRebind); } } + this.isLastIndex = false; + this._lastRebind = this._first; + var _movedViewsCount = this._moveViews(_viewsToMove); + this.movedViewsCount = _movedViewsCount; + var _adjustHeight = _movedViewsCount < _viewsToMove ? this._topBufferHeight : itemHeight * _movedViewsCount; + this._switchedDirection = false; + this._topBufferHeight = this._topBufferHeight - _adjustHeight; + this._bottomBufferHeight = this._bottomBufferHeight + _adjustHeight; + if (this._topBufferHeight >= 0) { + this._adjustBufferHeights(); + } + } this._previousFirst = this._first; this._ticking = false; }; - VirtualRepeat.prototype._handleResize = function _handleResize() { - var children = this.viewSlot.children, - childrenLength = children.length, - overrideContext, - view, - addIndex; - - this.scrollContainerHeight = _utilities.calcScrollHeight(this.scrollContainer); - this._viewsLength = Math.ceil(this.scrollContainerHeight / this.itemHeight) + 1; - - if (this._viewsLength > childrenLength) { - addIndex = children[childrenLength - 1].overrideContext.$index + 1; - overrideContext = _aureliaTemplatingResourcesRepeatUtilities.createFullOverrideContext(this, this.items[addIndex], addIndex, this.items.length); - view = this.viewFactory.create(); - view.bind(overrideContext.bindingContext, overrideContext); - this.viewSlot.insert(childrenLength, view); - } else if (this._viewsLength < childrenLength) { - this._viewsLength = childrenLength; - } - }; - VirtualRepeat.prototype._checkScrolling = function _checkScrolling() { if (this._first > this._previousFirst && (this._bottomBufferHeight > 0 || !this.isLastIndex)) { if (!this._scrollingDown) { @@ -269,7 +302,7 @@ var VirtualRepeat = (function () { this._switchedDirection = false; } this._isScrolling = true; - } else if (this._first < this._previousFirst && (this._topBufferHeight >= 0 || !this.isAtTop)) { + } else if (this._first < this._previousFirst && (this._topBufferHeight >= 0 || !this._isAtTop)) { if (!this._scrollingUp) { this._scrollingDown = false; this._scrollingUp = true; @@ -283,27 +316,9 @@ var VirtualRepeat = (function () { } }; - VirtualRepeat.prototype._hasScrolledDownTheBuffer = function _hasScrolledDownTheBuffer() { - var atBottom = this._first + this._viewsLength >= this.items.length; - var itemsAddedWhileAtBottom = atBottom && this._first > this._lastRebind; - return this._first - this._lastRebind >= this._bufferSize || itemsAddedWhileAtBottom; - }; - - VirtualRepeat.prototype._hasScrolledDownTheBufferFromTop = function _hasScrolledDownTheBufferFromTop() { - return this._first - this._bufferSize > 0; - }; - - VirtualRepeat.prototype._hasScrolledUpTheBuffer = function _hasScrolledUpTheBuffer() { - return this._lastRebind - this._first >= this._bufferSize; - }; - - VirtualRepeat.prototype._hasScrolledUpTheBufferFromBottom = function _hasScrolledUpTheBufferFromBottom() { - return this._first + this._bufferSize < this.items.length; - }; - VirtualRepeat.prototype._adjustBufferHeights = function _adjustBufferHeights() { this.topBuffer.setAttribute('style', 'height: ' + this._topBufferHeight + 'px'); - this.bottomBuffer.setAttribute("style", 'height: ' + this._bottomBufferHeight + 'px'); + this.bottomBuffer.setAttribute('style', 'height: ' + this._bottomBufferHeight + 'px'); }; VirtualRepeat.prototype._unsubscribeCollection = function _unsubscribeCollection() { @@ -323,63 +338,69 @@ var VirtualRepeat = (function () { return index - i; }; var isAtFirstOrLastIndex = function isAtFirstOrLastIndex() { - return _this5._scrollingDown ? _this5.isLastIndex : _this5.isAtTop; + return _this5._scrollingDown ? _this5.isLastIndex : _this5._isAtTop; }; - var viewSlot = this.viewSlot; - var childrenLength = viewSlot.children.length; + var childrenLength = this.viewCount(); var viewIndex = this._scrollingDown ? 0 : childrenLength - 1; var items = this.items; - var scrollList = this.scrollList; var index = this._scrollingDown ? this._getIndexOfLastView() + 1 : this._getIndexOfFirstView() - 1; var i = 0; while (i < length && !isAtFirstOrLastIndex()) { - var view = viewSlot.children[viewIndex]; + var view = this.view(viewIndex); var nextIndex = getNextIndex(index, i); - _aureliaTemplatingResourcesRepeatUtilities.updateOverrideContext(view.overrideContext, nextIndex, items.length); - view.bindingContext[this.local] = items[nextIndex]; - if (this._scrollingDown) { - viewSlot.children.push(viewSlot.children.shift()); - this.viewStrategy.moveViewLast(view, scrollList, childrenLength); - this.isLastIndex = nextIndex >= items.length - 1; - } else { - viewSlot.children.unshift(viewSlot.children.splice(-1, 1)[0]); - this.viewStrategy.moveViewFirst(view, scrollList); - this.isAtTop = nextIndex <= 0; + this.isLastIndex = nextIndex >= items.length - 1; + this._isAtTop = nextIndex <= 0; + if (!(isAtFirstOrLastIndex() && childrenLength >= items.length)) { + (0, _utilities.rebindAndMoveView)(this, view, nextIndex, this._scrollingDown); + i++; } - i++; } + return length - (length - i); }; VirtualRepeat.prototype._getIndexOfLastView = function _getIndexOfLastView() { - var children = this.viewSlot.children; - return children[children.length - 1].overrideContext.$index; + return this.view(this.viewCount() - 1).overrideContext.$index; }; VirtualRepeat.prototype._getIndexOfFirstView = function _getIndexOfFirstView() { - var children = this.viewSlot.children; - return children[0].overrideContext.$index; + return this.view(0) ? this.view(0).overrideContext.$index : -1; }; - VirtualRepeat.prototype._calcInitialHeights = function _calcInitialHeights() { - if (this._viewsLength > 0 && this._itemsLength == this.items.length) { + VirtualRepeat.prototype._calcInitialHeights = function _calcInitialHeights(itemsLength) { + if (this._viewsLength > 0 && this._itemsLength === itemsLength || itemsLength <= 0) { return; } - this._itemsLength = this.items.length; - var listItems = this.scrollList.children; - this.itemHeight = _utilities.calcOuterHeight(listItems[1]); - this.scrollContainerHeight = _utilities.calcScrollHeight(this.scrollContainer); + this._hasCalculatedSizes = true; + this._itemsLength = itemsLength; + var firstViewElement = this.view(0).firstChild.nextElementSibling; + this.itemHeight = (0, _utilities.calcOuterHeight)(firstViewElement); + if (this.itemHeight <= 0) { + throw new Error('Could not calculate item height'); + } + this.scrollContainerHeight = this._fixedHeightContainer ? this._calcScrollHeight(this.scrollContainer) : document.documentElement.clientHeight; this.elementsInView = Math.ceil(this.scrollContainerHeight / this.itemHeight) + 1; this._viewsLength = this.elementsInView * 2 + this._bufferSize; - this._bottomBufferHeight = this.itemHeight * this.items.length - this.itemHeight * this._viewsLength; - this.bottomBuffer.setAttribute("style", 'height: ' + this._bottomBufferHeight + 'px'); + this._bottomBufferHeight = this.itemHeight * itemsLength - this.itemHeight * this._viewsLength; + if (this._bottomBufferHeight < 0) { + this._bottomBufferHeight = 0; + } + this.bottomBuffer.setAttribute('style', 'height: ' + this._bottomBufferHeight + 'px'); this._topBufferHeight = 0; - this.topBuffer.setAttribute("style", 'height: ' + this._topBufferHeight + 'px'); + this.topBuffer.setAttribute('style', 'height: ' + this._topBufferHeight + 'px'); this.scrollContainer.scrollTop = 0; this._first = 0; }; + VirtualRepeat.prototype._calcScrollHeight = function _calcScrollHeight(element) { + var height = void 0; + height = element.getBoundingClientRect().height; + height -= (0, _utilities.getStyleValue)(element, 'borderTopWidth'); + height -= (0, _utilities.getStyleValue)(element, 'borderBottomWidth'); + return height; + }; + VirtualRepeat.prototype._observeInnerCollection = function _observeInnerCollection() { var items = this._getInnerCollection(); var strategy = this.strategyLocator.getStrategy(items); @@ -396,7 +417,7 @@ var VirtualRepeat = (function () { }; VirtualRepeat.prototype._getInnerCollection = function _getInnerCollection() { - var expression = _aureliaTemplatingResourcesRepeatUtilities.unwrapExpression(this.sourceExpression); + var expression = (0, _repeatUtilities.unwrapExpression)(this.sourceExpression); if (!expression) { return null; } @@ -412,11 +433,58 @@ var VirtualRepeat = (function () { } }; - var _VirtualRepeat = VirtualRepeat; - VirtualRepeat = _aureliaDependencyInjection.inject(Element, _aureliaTemplating.BoundViewFactory, _aureliaTemplating.TargetInstruction, _aureliaTemplating.ViewSlot, _aureliaBinding.ObserverLocator, _virtualRepeatStrategyLocator.VirtualRepeatStrategyLocator, _viewStrategy.ViewStrategyLocator)(VirtualRepeat) || VirtualRepeat; - VirtualRepeat = _aureliaTemplating.templateController(VirtualRepeat) || VirtualRepeat; - VirtualRepeat = _aureliaTemplating.customAttribute('virtual-repeat')(VirtualRepeat) || VirtualRepeat; - return VirtualRepeat; -})(); + VirtualRepeat.prototype.viewCount = function viewCount() { + return this.viewSlot.children.length; + }; + + VirtualRepeat.prototype.views = function views() { + return this.viewSlot.children; + }; + + VirtualRepeat.prototype.view = function view(index) { + return this.viewSlot.children[index]; + }; + + VirtualRepeat.prototype.addView = function addView(bindingContext, overrideContext) { + var view = this.viewFactory.create(); + view.bind(bindingContext, overrideContext); + this.viewSlot.add(view); + }; + + VirtualRepeat.prototype.insertView = function insertView(index, bindingContext, overrideContext) { + var view = this.viewFactory.create(); + view.bind(bindingContext, overrideContext); + this.viewSlot.insert(index, view); + }; -exports.VirtualRepeat = VirtualRepeat; \ No newline at end of file + VirtualRepeat.prototype.removeAllViews = function removeAllViews(returnToCache, skipAnimation) { + return this.viewSlot.removeAll(returnToCache, skipAnimation); + }; + + VirtualRepeat.prototype.removeView = function removeView(index, returnToCache, skipAnimation) { + return this.viewSlot.removeAt(index, returnToCache, skipAnimation); + }; + + VirtualRepeat.prototype.updateBindings = function updateBindings(view) { + var j = view.bindings.length; + while (j--) { + (0, _repeatUtilities.updateOneTimeBinding)(view.bindings[j]); + } + j = view.controllers.length; + while (j--) { + var k = view.controllers[j].boundProperties.length; + while (k--) { + var binding = view.controllers[j].boundProperties[k].binding; + (0, _repeatUtilities.updateOneTimeBinding)(binding); + } + } + }; + + return VirtualRepeat; +}(_aureliaTemplatingResources.AbstractRepeater), (_descriptor = _applyDecoratedDescriptor(_class2.prototype, 'items', [_aureliaTemplating.bindable], { + enumerable: true, + initializer: null +}), _descriptor2 = _applyDecoratedDescriptor(_class2.prototype, 'local', [_aureliaTemplating.bindable], { + enumerable: true, + initializer: null +})), _class2)) || _class) || _class) || _class); \ No newline at end of file diff --git a/dist/es2015/array-virtual-repeat-strategy.js b/dist/es2015/array-virtual-repeat-strategy.js new file mode 100644 index 0000000..a1448e2 --- /dev/null +++ b/dist/es2015/array-virtual-repeat-strategy.js @@ -0,0 +1,222 @@ +import { ArrayRepeatStrategy } from 'aurelia-templating-resources/array-repeat-strategy'; +import { createFullOverrideContext } from 'aurelia-templating-resources/repeat-utilities'; +import { updateVirtualOverrideContexts, rebindAndMoveView, getElementDistanceToBottomViewPort } from './utilities'; + +export let ArrayVirtualRepeatStrategy = class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy { + createFirstItem(repeat) { + let overrideContext = createFullOverrideContext(repeat, repeat.items[0], 0, 1); + repeat.addView(overrideContext.bindingContext, overrideContext); + } + + instanceChanged(repeat, items) { + this._inPlaceProcessItems(repeat, items); + } + + _standardProcessInstanceChanged(repeat, items) { + for (let i = 1, ii = repeat._viewsLength; i < ii; ++i) { + let overrideContext = createFullOverrideContext(repeat, items[i], i, ii); + repeat.addView(overrideContext.bindingContext, overrideContext); + } + } + + _inPlaceProcessItems(repeat, items) { + let itemsLength = items.length; + let viewsLength = repeat.viewCount(); + let first = repeat._getIndexOfFirstView(); + + while (viewsLength > repeat._viewsLength) { + viewsLength--; + repeat.removeView(viewsLength, true); + } + + let local = repeat.local; + + for (let i = 0; i < viewsLength; i++) { + let view = repeat.view(i); + let last = i === itemsLength - 1; + let middle = i !== 0 && !last; + + if (view.bindingContext[local] === items[i + first] && view.overrideContext.$middle === middle && view.overrideContext.$last === last) { + continue; + } + + view.bindingContext[local] = items[i + first]; + view.overrideContext.$middle = middle; + view.overrideContext.$last = last; + repeat.updateBindings(view); + } + + let minLength = Math.min(repeat._viewsLength, items.length); + for (let i = viewsLength; i < minLength; i++) { + let overrideContext = createFullOverrideContext(repeat, items[i], i, itemsLength); + repeat.addView(overrideContext.bindingContext, overrideContext); + } + } + + instanceMutated(repeat, array, splices) { + this._standardProcessInstanceMutated(repeat, array, splices); + } + + _standardProcessInstanceMutated(repeat, array, splices) { + if (repeat.__queuedSplices) { + for (let i = 0, ii = splices.length; i < ii; ++i) { + let { index, removed, addedCount } = splices[i]; + mergeSplice(repeat.__queuedSplices, index, removed, addedCount); + } + repeat.__array = array.slice(0); + return; + } + + let maybePromise = this._runSplices(repeat, array.slice(0), splices); + if (maybePromise instanceof Promise) { + let queuedSplices = repeat.__queuedSplices = []; + + let runQueuedSplices = () => { + if (!queuedSplices.length) { + delete repeat.__queuedSplices; + delete repeat.__array; + return; + } + + let nextPromise = this._runSplices(repeat, repeat.__array, queuedSplices) || Promise.resolve(); + nextPromise.then(runQueuedSplices); + }; + + maybePromise.then(runQueuedSplices); + } + } + + _runSplices(repeat, array, splices) { + let removeDelta = 0; + let rmPromises = []; + + for (let i = 0, ii = splices.length; i < ii; ++i) { + let splice = splices[i]; + let removed = splice.removed; + for (let j = 0, jj = removed.length; j < jj; ++j) { + let viewOrPromise = this._removeViewAt(repeat, splice.index + removeDelta + rmPromises.length, true); + if (viewOrPromise instanceof Promise) { + rmPromises.push(viewOrPromise); + } + } + removeDelta -= splice.addedCount; + } + + if (rmPromises.length > 0) { + return Promise.all(rmPromises).then(() => { + this._handleAddedSplices(repeat, array, splices); + updateVirtualOverrideContexts(repeat, 0); + }); + } + this._handleAddedSplices(repeat, array, splices); + updateVirtualOverrideContexts(repeat, 0); + } + + _removeViewAt(repeat, collectionIndex, returnToCache) { + let viewOrPromise; + let view; + let viewSlot = repeat.viewSlot; + let viewCount = repeat.viewCount(); + let viewAddIndex; + + if (!this._isIndexBeforeViewSlot(repeat, viewSlot, collectionIndex) && !this._isIndexAfterViewSlot(repeat, viewSlot, collectionIndex)) { + let viewIndex = this._getViewIndex(repeat, viewSlot, collectionIndex); + viewOrPromise = repeat.removeView(viewIndex, returnToCache); + if (repeat.items.length > viewCount) { + let collectionAddIndex; + if (repeat._bottomBufferHeight > repeat.itemHeight) { + viewAddIndex = viewCount; + collectionAddIndex = repeat._getIndexOfLastView() + 1; + repeat._bottomBufferHeight = repeat._bottomBufferHeight - repeat.itemHeight; + } else if (repeat._topBufferHeight > 0) { + viewAddIndex = 0; + collectionAddIndex = repeat._getIndexOfFirstView() - 1; + repeat._topBufferHeight = repeat._topBufferHeight - repeat.itemHeight; + } + let data = repeat.items[collectionAddIndex]; + if (data) { + let overrideContext = createFullOverrideContext(repeat, repeat.items[collectionAddIndex], collectionAddIndex, repeat.items.length); + view = repeat.viewFactory.create(); + view.bind(overrideContext.bindingContext, overrideContext); + } + } else { + return viewOrPromise; + } + } else if (this._isIndexBeforeViewSlot(repeat, viewSlot, collectionIndex)) { + if (repeat._bottomBufferHeight > 0) { + repeat._bottomBufferHeight = repeat._bottomBufferHeight - repeat.itemHeight; + rebindAndMoveView(repeat, repeat.view(0), repeat.view(0).overrideContext.$index, true); + } else { + repeat._topBufferHeight = repeat._topBufferHeight - repeat.itemHeight; + } + } else if (this._isIndexAfterViewSlot(repeat, viewSlot, collectionIndex)) { + repeat._bottomBufferHeight = repeat._bottomBufferHeight - repeat.itemHeight; + } + + if (viewOrPromise instanceof Promise) { + viewOrPromise.then(() => { + repeat.viewSlot.insert(viewAddIndex, view); + repeat._adjustBufferHeights(); + }); + return undefined; + } else if (view) { + repeat.viewSlot.insert(viewAddIndex, view); + } + + repeat._adjustBufferHeights(); + } + + _isIndexBeforeViewSlot(repeat, viewSlot, index) { + let viewIndex = this._getViewIndex(repeat, viewSlot, index); + return viewIndex < 0; + } + + _isIndexAfterViewSlot(repeat, viewSlot, index) { + let viewIndex = this._getViewIndex(repeat, viewSlot, index); + return viewIndex > repeat._viewsLength - 1; + } + + _getViewIndex(repeat, viewSlot, index) { + if (repeat.viewCount() === 0) { + return -1; + } + + let topBufferItems = repeat._topBufferHeight / repeat.itemHeight; + return index - topBufferItems; + } + + _handleAddedSplices(repeat, array, splices) { + let arrayLength = array.length; + let viewSlot = repeat.viewSlot; + for (let i = 0, ii = splices.length; i < ii; ++i) { + let splice = splices[i]; + let addIndex = splice.index; + let end = splice.index + splice.addedCount; + for (; addIndex < end; ++addIndex) { + let hasDistanceToBottomViewPort = getElementDistanceToBottomViewPort(repeat.bottomBuffer.previousElementSibling) > 0; + if (repeat.viewCount() === 0 || !this._isIndexBeforeViewSlot(repeat, viewSlot, addIndex) && !this._isIndexAfterViewSlot(repeat, viewSlot, addIndex) || hasDistanceToBottomViewPort) { + let overrideContext = createFullOverrideContext(repeat, array[addIndex], addIndex, arrayLength); + repeat.insertView(addIndex, overrideContext.bindingContext, overrideContext); + if (!repeat._hasCalculatedSizes) { + repeat._calcInitialHeights(1); + } else if (repeat.viewCount() > repeat._viewsLength) { + if (hasDistanceToBottomViewPort) { + repeat.removeView(0, true, true); + repeat._topBufferHeight = repeat._topBufferHeight + repeat.itemHeight; + repeat._adjustBufferHeights(); + } else { + repeat.removeView(repeat.viewCount() - 1, true, true); + repeat._bottomBufferHeight = repeat._bottomBufferHeight + repeat.itemHeight; + } + } + } else if (this._isIndexBeforeViewSlot(repeat, viewSlot, addIndex)) { + repeat._topBufferHeight = repeat._topBufferHeight + repeat.itemHeight; + } else if (this._isIndexAfterViewSlot(repeat, viewSlot, addIndex)) { + repeat._bottomBufferHeight = repeat._bottomBufferHeight + repeat.itemHeight; + repeat.isLastIndex = false; + } + } + } + repeat._adjustBufferHeights(); + } +}; \ No newline at end of file diff --git a/dist/es2015/aurelia-ui-virtualization.d.ts b/dist/es2015/aurelia-ui-virtualization.d.ts new file mode 100644 index 0000000..cc50ca3 --- /dev/null +++ b/dist/es2015/aurelia-ui-virtualization.d.ts @@ -0,0 +1,131 @@ +declare module 'aurelia-ui-virtualization' { + import { + updateOverrideContext, + createFullOverrideContext, + getItemsSourceExpression, + isOneTime, + unwrapExpression, + updateOneTimeBinding + } from 'aurelia-templating-resources/repeat-utilities'; + import { + ArrayRepeatStrategy + } from 'aurelia-templating-resources/array-repeat-strategy'; + import { + RepeatStrategyLocator + } from 'aurelia-templating-resources/repeat-strategy-locator'; + import { + inject + } from 'aurelia-dependency-injection'; + import { + ObserverLocator + } from 'aurelia-binding'; + import { + BoundViewFactory, + ViewSlot, + TargetInstruction, + customAttribute, + bindable, + templateController + } from 'aurelia-templating'; + import { + AbstractRepeater + } from 'aurelia-templating-resources'; + import { + viewsRequireLifecycle + } from 'aurelia-templating-resources/analyze-view-factory'; + export function calcOuterHeight(element: any): any; + export function insertBeforeNode(view: any, bottomBuffer: any): any; + + /** + * Update the override context. + * @param startIndex index in collection where to start updating. + */ + export function updateVirtualOverrideContexts(repeat: any, startIndex: any): any; + export function rebindAndMoveView(repeat: VirtualRepeat, view: View, index: number, moveToBottom: boolean): void; + export function getStyleValue(element: any, style: any): any; + export function getElementDistanceToBottomViewPort(element: any): any; + + /** + * A strategy for repeating a template over an array. + */ + export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy { + + // create first item to calculate the heights + createFirstItem(repeat: any): any; + + /** + * Handle the repeat's collection instance changing. + * @param repeat The repeater instance. + * @param items The new array instance. + */ + instanceChanged(repeat: any, items: any): any; + + /** + * Handle the repeat's collection instance mutating. + * @param repeat The repeat instance. + * @param array The modified array. + * @param splices Records of array changes. + */ + instanceMutated(repeat: any, array: any, splices: any): any; + } + export class ViewStrategyLocator { + getStrategy(element: any): any; + } + export class TableStrategy { + getScrollContainer(element: any): any; + moveViewFirst(view: any, topBuffer: any): any; + moveViewLast(view: any, bottomBuffer: any): any; + createTopBufferElement(element: any): any; + createBottomBufferElement(element: any): any; + removeBufferElements(element: any, topBuffer: any, bottomBuffer: any): any; + } + export class DefaultStrategy { + getScrollContainer(element: any): any; + moveViewFirst(view: any, topBuffer: any): any; + moveViewLast(view: any, bottomBuffer: any): any; + createTopBufferElement(element: any): any; + createBottomBufferElement(element: any): any; + removeBufferElements(element: any, topBuffer: any, bottomBuffer: any): any; + } + export class VirtualRepeatStrategyLocator extends RepeatStrategyLocator { + constructor(); + } + export class VirtualRepeat extends AbstractRepeater { + _first: any; + _previousFirst: any; + _viewsLength: any; + _lastRebind: any; + _topBufferHeight: any; + _bottomBufferHeight: any; + _bufferSize: any; + _scrollingDown: any; + _scrollingUp: any; + _switchedDirection: any; + _isAttached: any; + _ticking: any; + _fixedHeightContainer: any; + _hasCalculatedSizes: any; + _isAtTop: any; + items: any; + local: any; + constructor(element: any, viewFactory: any, instruction: any, viewSlot: any, observerLocator: any, strategyLocator: any, viewStrategyLocator: any); + attached(): any; + bind(bindingContext: any, overrideContext: any): any; + call(context: any, changes: any): any; + detached(): any; + itemsChanged(): any; + unbind(): any; + handleCollectionMutated(collection: any, changes: any): any; + handleInnerCollectionMutated(collection: any, changes: any): any; + + // @override AbstractRepeater + viewCount(): any; + views(): any; + view(index: any): any; + addView(bindingContext: any, overrideContext: any): any; + insertView(index: any, bindingContext: any, overrideContext: any): any; + removeAllViews(returnToCache: any, skipAnimation: any): any; + removeView(index: any, returnToCache: any, skipAnimation: any): any; + updateBindings(view: View): any; + } +} \ No newline at end of file diff --git a/dist/es2015/index.js b/dist/es2015/index.js new file mode 100644 index 0000000..91b4a83 --- /dev/null +++ b/dist/es2015/index.js @@ -0,0 +1,7 @@ +import { VirtualRepeat } from './virtual-repeat'; + +export function configure(config) { + config.globalResources('./virtual-repeat'); +} + +export { VirtualRepeat }; \ No newline at end of file diff --git a/dist/es2015/utilities.js b/dist/es2015/utilities.js new file mode 100644 index 0000000..c124066 --- /dev/null +++ b/dist/es2015/utilities.js @@ -0,0 +1,62 @@ +import { updateOverrideContext } from 'aurelia-templating-resources/repeat-utilities'; + +export function calcOuterHeight(element) { + let height; + height = element.getBoundingClientRect().height; + height += getStyleValue(element, 'marginTop'); + height += getStyleValue(element, 'marginBottom'); + return height; +} + +export function insertBeforeNode(view, bottomBuffer) { + let viewStart = view.firstChild; + let element = viewStart.nextSibling; + let viewEnd = view.lastChild; + let parentElement = bottomBuffer.parentElement; + + parentElement.insertBefore(viewEnd, bottomBuffer); + parentElement.insertBefore(element, viewEnd); + parentElement.insertBefore(viewStart, element); +} + +export function updateVirtualOverrideContexts(repeat, startIndex) { + let views = repeat.viewSlot.children; + let viewLength = views.length; + let collectionLength = repeat.items.length; + + if (startIndex > 0) { + startIndex = startIndex - 1; + } + + let delta = repeat._topBufferHeight / repeat.itemHeight; + + for (; startIndex < viewLength; ++startIndex) { + updateOverrideContext(views[startIndex].overrideContext, startIndex + delta, collectionLength); + } +} + +export function rebindAndMoveView(repeat, view, index, moveToBottom) { + let items = repeat.items; + let viewSlot = repeat.viewSlot; + updateOverrideContext(view.overrideContext, index, items.length); + view.bindingContext[repeat.local] = items[index]; + if (moveToBottom) { + viewSlot.children.push(viewSlot.children.shift()); + repeat.viewStrategy.moveViewLast(view, repeat.bottomBuffer); + } else { + viewSlot.children.unshift(viewSlot.children.splice(-1, 1)[0]); + repeat.viewStrategy.moveViewFirst(view, repeat.topBuffer); + } +} + +export function getStyleValue(element, style) { + let currentStyle; + let styleValue; + currentStyle = element.currentStyle || window.getComputedStyle(element); + styleValue = parseInt(currentStyle[style], 10); + return Number.isNaN(styleValue) ? 0 : styleValue; +} + +export function getElementDistanceToBottomViewPort(element) { + return document.documentElement.clientHeight - element.getBoundingClientRect().bottom; +} \ No newline at end of file diff --git a/dist/es2015/view-strategy.js b/dist/es2015/view-strategy.js new file mode 100644 index 0000000..2df3c5f --- /dev/null +++ b/dist/es2015/view-strategy.js @@ -0,0 +1,84 @@ +import { insertBeforeNode } from './utilities'; + +export let ViewStrategyLocator = class ViewStrategyLocator { + getStrategy(element) { + if (element.parentNode.localName === 'tbody') { + return new TableStrategy(); + } + return new DefaultStrategy(); + } +}; + +export let TableStrategy = class TableStrategy { + getScrollContainer(element) { + return element.parentNode; + } + + moveViewFirst(view, topBuffer) { + insertBeforeNode(view, topBuffer.parentElement.nextElementSibling.previousSibling); + } + + moveViewLast(view, bottomBuffer) { + insertBeforeNode(view, bottomBuffer.parentElement); + } + + createTopBufferElement(element) { + let tr = document.createElement('tr'); + let buffer = document.createElement('td'); + buffer.setAttribute('style', 'height: 0px'); + tr.appendChild(buffer); + element.parentElement.insertBefore(tr, element); + return buffer; + } + + createBottomBufferElement(element) { + let tr = document.createElement('tr'); + let buffer = document.createElement('td'); + buffer.setAttribute('style', 'height: 0px'); + tr.appendChild(buffer); + element.parentNode.insertBefore(tr, element.nextSibling); + return buffer; + } + + removeBufferElements(element, topBuffer, bottomBuffer) { + element.parentElement.removeChild(topBuffer.parentElement); + element.parentElement.removeChild(bottomBuffer.parentElement); + } +}; + +export let DefaultStrategy = class DefaultStrategy { + getScrollContainer(element) { + return element.parentNode; + } + + moveViewFirst(view, topBuffer) { + insertBeforeNode(view, topBuffer.nextElementSibling.previousSibling); + } + + moveViewLast(view, bottomBuffer) { + let previousSibling = bottomBuffer.previousSibling; + let referenceNode = previousSibling.nodeType === 8 && previousSibling.data === 'anchor' ? previousSibling : bottomBuffer; + insertBeforeNode(view, referenceNode); + } + + createTopBufferElement(element) { + let elementName = element.parentElement.localName === 'ul' ? 'li' : 'div'; + let buffer = document.createElement(elementName); + buffer.setAttribute('style', 'height: 0px'); + element.parentElement.insertBefore(buffer, element); + return buffer; + } + + createBottomBufferElement(element) { + let elementName = element.parentElement.localName === 'ul' ? 'li' : 'div'; + let buffer = document.createElement(elementName); + buffer.setAttribute('style', 'height: 0px'); + element.parentNode.insertBefore(buffer, element.nextSibling); + return buffer; + } + + removeBufferElements(element, topBuffer, bottomBuffer) { + element.parentElement.removeChild(topBuffer); + element.parentElement.removeChild(bottomBuffer); + } +}; \ No newline at end of file diff --git a/dist/es2015/virtual-repeat-strategy-locator.js b/dist/es2015/virtual-repeat-strategy-locator.js new file mode 100644 index 0000000..7b08a88 --- /dev/null +++ b/dist/es2015/virtual-repeat-strategy-locator.js @@ -0,0 +1,12 @@ +import { RepeatStrategyLocator } from 'aurelia-templating-resources/repeat-strategy-locator'; +import { ArrayVirtualRepeatStrategy } from './array-virtual-repeat-strategy'; + +export let VirtualRepeatStrategyLocator = class VirtualRepeatStrategyLocator extends RepeatStrategyLocator { + constructor() { + super(); + this.matchers = []; + this.strategies = []; + + this.addStrategy(items => items instanceof Array, new ArrayVirtualRepeatStrategy()); + } +}; \ No newline at end of file diff --git a/dist/es2015/virtual-repeat.js b/dist/es2015/virtual-repeat.js new file mode 100644 index 0000000..371b21e --- /dev/null +++ b/dist/es2015/virtual-repeat.js @@ -0,0 +1,440 @@ +var _dec, _dec2, _class, _desc, _value, _class2, _descriptor, _descriptor2; + +function _initDefineProp(target, property, descriptor, context) { + if (!descriptor) return; + Object.defineProperty(target, property, { + enumerable: descriptor.enumerable, + configurable: descriptor.configurable, + writable: descriptor.writable, + value: descriptor.initializer ? descriptor.initializer.call(context) : void 0 + }); +} + +function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } + + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; +} + +function _initializerWarningHelper(descriptor, context) { + throw new Error('Decorating class property failed. Please ensure that transform-class-properties is enabled.'); +} + +import { inject } from 'aurelia-dependency-injection'; +import { ObserverLocator } from 'aurelia-binding'; +import { BoundViewFactory, ViewSlot, TargetInstruction, customAttribute, bindable, templateController } from 'aurelia-templating'; +import { AbstractRepeater } from 'aurelia-templating-resources'; +import { getItemsSourceExpression, isOneTime, unwrapExpression, updateOneTimeBinding } from 'aurelia-templating-resources/repeat-utilities'; +import { viewsRequireLifecycle } from 'aurelia-templating-resources/analyze-view-factory'; +import { getStyleValue, calcOuterHeight, rebindAndMoveView } from './utilities'; +import { VirtualRepeatStrategyLocator } from './virtual-repeat-strategy-locator'; +import { ViewStrategyLocator } from './view-strategy'; + +export let VirtualRepeat = (_dec = customAttribute('virtual-repeat'), _dec2 = inject(Element, BoundViewFactory, TargetInstruction, ViewSlot, ObserverLocator, VirtualRepeatStrategyLocator, ViewStrategyLocator), _dec(_class = templateController(_class = _dec2(_class = (_class2 = class VirtualRepeat extends AbstractRepeater { + constructor(element, viewFactory, instruction, viewSlot, observerLocator, strategyLocator, viewStrategyLocator) { + super({ + local: 'item', + viewsRequireLifecycle: viewsRequireLifecycle(viewFactory) + }); + + this._first = 0; + this._previousFirst = 0; + this._viewsLength = 0; + this._lastRebind = 0; + this._topBufferHeight = 0; + this._bottomBufferHeight = 0; + this._bufferSize = 5; + this._scrollingDown = false; + this._scrollingUp = false; + this._switchedDirection = false; + this._isAttached = false; + this._ticking = false; + this._fixedHeightContainer = false; + this._hasCalculatedSizes = false; + this._isAtTop = true; + + _initDefineProp(this, 'items', _descriptor, this); + + _initDefineProp(this, 'local', _descriptor2, this); + + this.element = element; + this.viewFactory = viewFactory; + this.instruction = instruction; + this.viewSlot = viewSlot; + this.observerLocator = observerLocator; + this.strategyLocator = strategyLocator; + this.viewStrategyLocator = viewStrategyLocator; + this.sourceExpression = getItemsSourceExpression(this.instruction, 'virtual-repeat.for'); + this.isOneTime = isOneTime(this.sourceExpression); + } + + attached() { + this._isAttached = true; + let element = this.element; + this.viewStrategy = this.viewStrategyLocator.getStrategy(element); + this.scrollContainer = this.viewStrategy.getScrollContainer(element); + this.topBuffer = this.viewStrategy.createTopBufferElement(element); + this.bottomBuffer = this.viewStrategy.createBottomBufferElement(element); + this.itemsChanged(); + this.scrollListener = () => this._onScroll(); + let containerStyle = this.scrollContainer.style; + if (containerStyle.overflowY === 'scroll' || containerStyle.overflow === 'scroll' || containerStyle.overflowY === 'auto' || containerStyle.overflow === 'auto') { + this._fixedHeightContainer = true; + this.scrollContainer.addEventListener('scroll', this.scrollListener); + } else { + document.addEventListener('scroll', this.scrollListener); + } + } + + bind(bindingContext, overrideContext) { + this.scope = { bindingContext, overrideContext }; + this._itemsLength = this.items.length; + } + + call(context, changes) { + this[context](this.items, changes); + } + + detached() { + this.scrollContainer.removeEventListener('scroll', this.scrollListener); + this._first = 0; + this._previousFirst = 0; + this._viewsLength = 0; + this._lastRebind = 0; + this._topBufferHeight = 0; + this._bottomBufferHeight = 0; + this._scrollingDown = false; + this._scrollingUp = false; + this._switchedDirection = false; + this._isAttached = false; + this._ticking = false; + this._hasCalculatedSizes = false; + this.viewStrategy.removeBufferElements(this.element, this.topBuffer, this.bottomBuffer); + this.isLastIndex = false; + this.scrollContainer = null; + this.scrollContainerHeight = null; + this.removeAllViews(true); + if (this.scrollHandler) { + this.scrollHandler.dispose(); + } + this._unsubscribeCollection(); + } + + itemsChanged() { + this._unsubscribeCollection(); + + if (!this.scope) { + return; + } + let items = this.items; + this.strategy = this.strategyLocator.getStrategy(items); + if (items.length > 0) { + this.strategy.createFirstItem(this); + } + this._calcInitialHeights(items.length); + if (!this.isOneTime && !this._observeInnerCollection()) { + this._observeCollection(); + } + + this.strategy.instanceChanged(this, items, this._viewsLength); + } + + unbind() { + this.scope = null; + this.items = null; + this._itemsLength = null; + } + + handleCollectionMutated(collection, changes) { + this._handlingMutations = true; + this._itemsLength = collection.length; + this.strategy.instanceMutated(this, collection, changes); + } + + handleInnerCollectionMutated(collection, changes) { + if (this.ignoreMutation) { + return; + } + this.ignoreMutation = true; + let newItems = this.sourceExpression.evaluate(this.scope, this.lookupFunctions); + this.observerLocator.taskQueue.queueMicroTask(() => this.ignoreMutation = false); + + if (newItems === this.items) { + this.itemsChanged(); + } else { + this.items = newItems; + } + } + + _onScroll() { + if (!this._ticking && !this._handlingMutations) { + requestAnimationFrame(() => this._handleScroll()); + this._ticking = true; + } + + if (this._handlingMutations) { + this._handlingMutations = false; + } + } + + _handleScroll() { + if (!this._isAttached) { + return; + } + let itemHeight = this.itemHeight; + let scrollTop = this._fixedHeightContainer ? this.scrollContainer.scrollTop : pageYOffset - this.topBuffer.offsetTop; + this._first = Math.floor(scrollTop / itemHeight); + this._first = this._first < 0 ? 0 : this._first; + if (this._first > this.items.length - this.elementsInView) { + this._first = this.items.length - this.elementsInView; + } + this._checkScrolling(); + + if (this._scrollingDown) { + let viewsToMove = this._first - this._lastRebind; + if (this._switchedDirection) { + viewsToMove = this._isAtTop ? this._first : this._bufferSize - (this._lastRebind - this._first); + } + this._isAtTop = false; + this._lastRebind = this._first; + let movedViewsCount = this._moveViews(viewsToMove); + let adjustHeight = movedViewsCount < viewsToMove ? this._bottomBufferHeight : itemHeight * movedViewsCount; + this._switchedDirection = false; + this._topBufferHeight = this._topBufferHeight + adjustHeight; + this._bottomBufferHeight = this._bottomBufferHeight - adjustHeight; + if (this._bottomBufferHeight >= 0) { + this._adjustBufferHeights(); + } + } else if (this._scrollingUp) { + let viewsToMove = this._lastRebind - this._first; + if (this._switchedDirection) { + if (this.isLastIndex) { + viewsToMove = this.items.length - this._first - this.elementsInView; + } else { + viewsToMove = this._bufferSize - (this._first - this._lastRebind); + } + } + this.isLastIndex = false; + this._lastRebind = this._first; + let movedViewsCount = this._moveViews(viewsToMove); + this.movedViewsCount = movedViewsCount; + let adjustHeight = movedViewsCount < viewsToMove ? this._topBufferHeight : itemHeight * movedViewsCount; + this._switchedDirection = false; + this._topBufferHeight = this._topBufferHeight - adjustHeight; + this._bottomBufferHeight = this._bottomBufferHeight + adjustHeight; + if (this._topBufferHeight >= 0) { + this._adjustBufferHeights(); + } + } + this._previousFirst = this._first; + + this._ticking = false; + } + + _checkScrolling() { + if (this._first > this._previousFirst && (this._bottomBufferHeight > 0 || !this.isLastIndex)) { + if (!this._scrollingDown) { + this._scrollingDown = true; + this._scrollingUp = false; + this._switchedDirection = true; + } else { + this._switchedDirection = false; + } + this._isScrolling = true; + } else if (this._first < this._previousFirst && (this._topBufferHeight >= 0 || !this._isAtTop)) { + if (!this._scrollingUp) { + this._scrollingDown = false; + this._scrollingUp = true; + this._switchedDirection = true; + } else { + this._switchedDirection = false; + } + this._isScrolling = true; + } else { + this._isScrolling = false; + } + } + + _adjustBufferHeights() { + this.topBuffer.setAttribute('style', `height: ${ this._topBufferHeight }px`); + this.bottomBuffer.setAttribute('style', `height: ${ this._bottomBufferHeight }px`); + } + + _unsubscribeCollection() { + if (this.collectionObserver) { + this.collectionObserver.unsubscribe(this.callContext, this); + this.collectionObserver = null; + this.callContext = null; + } + } + + _moveViews(length) { + let getNextIndex = this._scrollingDown ? (index, i) => index + i : (index, i) => index - i; + let isAtFirstOrLastIndex = () => this._scrollingDown ? this.isLastIndex : this._isAtTop; + let childrenLength = this.viewCount(); + let viewIndex = this._scrollingDown ? 0 : childrenLength - 1; + let items = this.items; + let index = this._scrollingDown ? this._getIndexOfLastView() + 1 : this._getIndexOfFirstView() - 1; + let i = 0; + while (i < length && !isAtFirstOrLastIndex()) { + let view = this.view(viewIndex); + let nextIndex = getNextIndex(index, i); + this.isLastIndex = nextIndex >= items.length - 1; + this._isAtTop = nextIndex <= 0; + if (!(isAtFirstOrLastIndex() && childrenLength >= items.length)) { + rebindAndMoveView(this, view, nextIndex, this._scrollingDown); + i++; + } + } + + return length - (length - i); + } + + _getIndexOfLastView() { + return this.view(this.viewCount() - 1).overrideContext.$index; + } + + _getIndexOfFirstView() { + return this.view(0) ? this.view(0).overrideContext.$index : -1; + } + + _calcInitialHeights(itemsLength) { + if (this._viewsLength > 0 && this._itemsLength === itemsLength || itemsLength <= 0) { + return; + } + this._hasCalculatedSizes = true; + this._itemsLength = itemsLength; + let firstViewElement = this.view(0).firstChild.nextElementSibling; + this.itemHeight = calcOuterHeight(firstViewElement); + if (this.itemHeight <= 0) { + throw new Error('Could not calculate item height'); + } + this.scrollContainerHeight = this._fixedHeightContainer ? this._calcScrollHeight(this.scrollContainer) : document.documentElement.clientHeight; + this.elementsInView = Math.ceil(this.scrollContainerHeight / this.itemHeight) + 1; + this._viewsLength = this.elementsInView * 2 + this._bufferSize; + this._bottomBufferHeight = this.itemHeight * itemsLength - this.itemHeight * this._viewsLength; + if (this._bottomBufferHeight < 0) { + this._bottomBufferHeight = 0; + } + this.bottomBuffer.setAttribute('style', `height: ${ this._bottomBufferHeight }px`); + this._topBufferHeight = 0; + this.topBuffer.setAttribute('style', `height: ${ this._topBufferHeight }px`); + + this.scrollContainer.scrollTop = 0; + this._first = 0; + } + + _calcScrollHeight(element) { + let height; + height = element.getBoundingClientRect().height; + height -= getStyleValue(element, 'borderTopWidth'); + height -= getStyleValue(element, 'borderBottomWidth'); + return height; + } + + _observeInnerCollection() { + let items = this._getInnerCollection(); + let strategy = this.strategyLocator.getStrategy(items); + if (!strategy) { + return false; + } + this.collectionObserver = strategy.getCollectionObserver(this.observerLocator, items); + if (!this.collectionObserver) { + return false; + } + this.callContext = 'handleInnerCollectionMutated'; + this.collectionObserver.subscribe(this.callContext, this); + return true; + } + + _getInnerCollection() { + let expression = unwrapExpression(this.sourceExpression); + if (!expression) { + return null; + } + return expression.evaluate(this.scope, null); + } + + _observeCollection() { + let items = this.items; + this.collectionObserver = this.strategy.getCollectionObserver(this.observerLocator, items); + if (this.collectionObserver) { + this.callContext = 'handleCollectionMutated'; + this.collectionObserver.subscribe(this.callContext, this); + } + } + + viewCount() { + return this.viewSlot.children.length; + } + views() { + return this.viewSlot.children; + } + view(index) { + return this.viewSlot.children[index]; + } + + addView(bindingContext, overrideContext) { + let view = this.viewFactory.create(); + view.bind(bindingContext, overrideContext); + this.viewSlot.add(view); + } + + insertView(index, bindingContext, overrideContext) { + let view = this.viewFactory.create(); + view.bind(bindingContext, overrideContext); + this.viewSlot.insert(index, view); + } + + removeAllViews(returnToCache, skipAnimation) { + return this.viewSlot.removeAll(returnToCache, skipAnimation); + } + + removeView(index, returnToCache, skipAnimation) { + return this.viewSlot.removeAt(index, returnToCache, skipAnimation); + } + + updateBindings(view) { + let j = view.bindings.length; + while (j--) { + updateOneTimeBinding(view.bindings[j]); + } + j = view.controllers.length; + while (j--) { + let k = view.controllers[j].boundProperties.length; + while (k--) { + let binding = view.controllers[j].boundProperties[k].binding; + updateOneTimeBinding(binding); + } + } + } +}, (_descriptor = _applyDecoratedDescriptor(_class2.prototype, 'items', [bindable], { + enumerable: true, + initializer: null +}), _descriptor2 = _applyDecoratedDescriptor(_class2.prototype, 'local', [bindable], { + enumerable: true, + initializer: null +})), _class2)) || _class) || _class) || _class); \ No newline at end of file diff --git a/dist/es6/array-virtual-repeat-strategy.js b/dist/es6/array-virtual-repeat-strategy.js deleted file mode 100644 index 6ffc78f..0000000 --- a/dist/es6/array-virtual-repeat-strategy.js +++ /dev/null @@ -1,220 +0,0 @@ -import {ArrayRepeatStrategy} from 'aurelia-templating-resources/array-repeat-strategy'; -import {createFullOverrideContext, updateOverrideContexts, updateOverrideContext, updateOneTimeBinding} from 'aurelia-templating-resources/repeat-utilities'; - -/** -* A strategy for repeating a template over an array. -*/ -export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy { - // create first item to calculate the heights - createFirstItem(repeat) { - var overrideContext = createFullOverrideContext(repeat, repeat.items[0], 0, 1); - var view = repeat.viewFactory.create(); - view.bind(overrideContext.bindingContext, overrideContext); - repeat.viewSlot.add(view); - } - /** - * Handle the repeat's collection instance changing. - * @param repeat The repeater instance. - * @param items The new array instance. - */ - instanceChanged(repeat, items) { - this._inPlaceProcessItems(repeat, items); - } - - _standardProcessInstanceChanged(repeat, items) { - for(var i = 1, ii = repeat._viewsLength; i < ii; ++i){ - let overrideContext = createFullOverrideContext(repeat, items[i], i, ii); - let view = repeat.viewFactory.create(); - view.bind(overrideContext.bindingContext, overrideContext); - repeat.viewSlot.add(view); - } - } - - _inPlaceProcessItems(repeat, items) { - let itemsLength = items.length; - let viewsLength = repeat.viewSlot.children.length; - let first = repeat._getIndexOfFirstView(); - // remove unneeded views. - while (viewsLength > repeat._viewsLength) { - viewsLength--; - repeat.viewSlot.removeAt(viewsLength, true); - } - // avoid repeated evaluating the property-getter for the "local" property. - let local = repeat.local; - // re-evaluate bindings on existing views. - for (let i = 0; i < viewsLength; i++) { - let view = repeat.viewSlot.children[i]; - let last = i === itemsLength - 1; - let middle = i !== 0 && !last; - // any changes to the binding context? - if (view.bindingContext[local] === items[i + first] && view.overrideContext.$middle === middle && view.overrideContext.$last === last) { - // no changes. continue... - continue; - } - // update the binding context and refresh the bindings. - view.bindingContext[local] = items[i + first]; - view.overrideContext.$middle = middle; - view.overrideContext.$last = last; - let j = view.bindings.length; - while (j--) { - updateOneTimeBinding(view.bindings[j]); - } - j = view.controllers.length; - while (j--) { - let k = view.controllers[j].boundProperties.length; - while (k--) { - let binding = view.controllers[j].boundProperties[k].binding; - updateOneTimeBinding(binding); - } - } - } - // add new views - for (let i = viewsLength; i < repeat._viewsLength; i++) { - let overrideContext = createFullOverrideContext(repeat, items[i], i, itemsLength); - let view = repeat.viewFactory.create(); - view.bind(overrideContext.bindingContext, overrideContext); - repeat.viewSlot.add(view); - } - } - - /** - * Handle the repeat's collection instance mutating. - * @param repeat The repeat instance. - * @param array The modified array. - * @param splices Records of array changes. - */ - instanceMutated(repeat, array, splices) { - this._updateViews(repeat, repeat.items, splices); - } - - _standardProcessInstanceMutated(repeat, array, splices) { - let removeDelta = 0; - let viewSlot = repeat.viewSlot; - let rmPromises = []; - - for (let i = 0, ii = splices.length; i < ii; ++i) { - let splice = splices[i]; - let removed = splice.removed; - let viewIndex = this._getViewIndex(repeat, viewSlot, splice.index); - if (viewIndex >= 0) { - for (let j = 0, jj = removed.length; j < jj; ++j) { - let viewOrPromise = viewSlot.removeAt(viewIndex + removeDelta + rmPromises.length, true); - - // TODO Create view without trigger view lifecycle - or better solution - let length = viewSlot.children.length; - let overrideContext = createFullOverrideContext(repeat, repeat.items[length], length, repeat.items.length); - let view = repeat.viewFactory.create(); - view.bind(overrideContext.bindingContext, overrideContext); - repeat.viewSlot.isAttached = false; - repeat.viewSlot.add(view); - repeat.viewSlot.isAttached = true; - - if (viewOrPromise instanceof Promise) { - rmPromises.push(viewOrPromise); - } - } - removeDelta -= splice.addedCount; - } - } - - if (rmPromises.length > 0) { - Promise.all(rmPromises).then(() => { - this._handleAddedSplices(repeat, array, splices); - this._updateViews(repeat, array, splices); - }); - } else { - this._handleAddedSplices(repeat, array, splices); - this._updateViews(repeat, array, splices); - } - } - - _updateViews(repeat, items, splices) { - let totalAdded = 0; - let totalRemoved = 0; - repeat.items = items; - - for(let i = 0, ii = splices.length; i < ii; ++i){ - let splice = splices[0]; - totalAdded += splice.addedCount; - totalRemoved += splice.removed.length; - } - - let index = repeat._getIndexOfFirstView() - totalRemoved; - - if(index < 0) { - index = 0; - } - - let viewSlot = repeat.viewSlot; - - for(let i = 0, ii = viewSlot.children.length; i < ii; ++i){ - let view = viewSlot.children[i]; - let nextIndex = index + i; - let itemsLength = items.length; - if((nextIndex) <= itemsLength - 1) { - view.bindingContext[repeat.local] = items[nextIndex]; - updateOverrideContext(view.overrideContext, nextIndex, itemsLength); - } - } - - let bufferDelta = repeat.itemHeight * totalAdded + repeat.itemHeight * -totalRemoved; - - if(repeat._bottomBufferHeight + bufferDelta < 0) { - repeat._topBufferHeight = repeat._topBufferHeight + bufferDelta; - } else { - repeat._bottomBufferHeight = repeat._bottomBufferHeight + bufferDelta; - } - - if(repeat._bottomBufferHeight > 0) { - repeat.isLastIndex = false; - } - - repeat._adjustBufferHeights(); - } - - _handleAddedSplices(repeat, array, splices) { - let spliceIndexLow; - let arrayLength = array.length; - for (let i = 0, ii = splices.length; i < ii; ++i) { - let splice = splices[i]; - let addIndex = splice.index; - let end = splice.index + splice.addedCount; - - if (typeof spliceIndexLow === 'undefined' || spliceIndexLow === null || spliceIndexLow > splice.index) { - spliceIndexLow = addIndex; - } - - for (; addIndex < end; ++addIndex) { - let overrideContext = createFullOverrideContext(repeat, array[addIndex], addIndex, arrayLength); - let view = repeat.viewFactory.create(); - view.bind(overrideContext.bindingContext, overrideContext); - repeat.viewSlot.insert(addIndex, view); - } - } - - return spliceIndexLow; - } - - _isIndexInDom(viewSlot, index) { - if(viewSlot.children.length === 0) { - return false; - } - - let indexLow = viewSlot.children[0].overrideContext.$index; - let indexHi = viewSlot.children[viewSlot.children.length - 1].overrideContext.$index; - - return index >= indexLow && index <= indexHi; - } - - _getViewIndex(repeat, viewSlot, index) { - if(viewSlot.children.length === 0) { - return -1; - } - let indexLow = viewSlot.children[0].overrideContext.$index; - let viewIndex = index - indexLow; - if(viewIndex > repeat._viewsLength - 1) { - viewIndex = -1; - } - return viewIndex; - } -} diff --git a/dist/es6/index.js b/dist/es6/index.js deleted file mode 100644 index 802ffc1..0000000 --- a/dist/es6/index.js +++ /dev/null @@ -1,14 +0,0 @@ -import {VirtualRepeat} from './virtual-repeat'; -import {VirtualList} from './virtual-list'; - -export function configure(config){ - config.globalResources( - './virtual-repeat', - './virtual-list' - ); -} - -export { - VirtualRepeat, - VirtualList -}; diff --git a/dist/es6/utilities.js b/dist/es6/utilities.js deleted file mode 100644 index 1eaf745..0000000 --- a/dist/es6/utilities.js +++ /dev/null @@ -1,32 +0,0 @@ -export function calcOuterHeight(element){ - var height; - height = element.getBoundingClientRect().height; - height += getStyleValue(element, 'marginTop'); - height += getStyleValue(element, 'marginBottom'); - return height; -} - -export function calcScrollHeight(element){ - var height; - height = element.getBoundingClientRect().height; - height -= getStyleValue(element, 'borderTopWidth'); - height -= getStyleValue(element, 'borderBottomWidth'); - return height; -} - -export function insertBeforeNode(view, scrollView, node) { - let viewStart = view.firstChild; - let element = viewStart.nextSibling; - let viewEnd = view.lastChild; - - scrollView.insertBefore(viewEnd, node); - scrollView.insertBefore(element, viewEnd); - scrollView.insertBefore(viewStart, element); -} - -function getStyleValue(element, style){ - var currentStyle, styleValue; - currentStyle = element.currentStyle || window.getComputedStyle(element); - styleValue = parseInt(currentStyle[style]); - return Number.isNaN(styleValue) ? 0 : styleValue; -} diff --git a/dist/es6/view-strategy.js b/dist/es6/view-strategy.js deleted file mode 100644 index 67c1895..0000000 --- a/dist/es6/view-strategy.js +++ /dev/null @@ -1,91 +0,0 @@ -import {insertBeforeNode} from './utilities'; - -export class ViewStrategyLocator { - getStrategy(element) { - if (element.parentNode.localName === 'tbody') { - return new TableStrategy(); - } else { - return new DefaultStrategy(); - } - } -} - -export class TableStrategy { - getScrollList(element) { - return element.parentNode; - } - - getScrollContainer(element) { - return this.getScrollList(element).parentElement.parentElement; - } - - moveViewFirst(view, scrollElement) { - insertBeforeNode(view, scrollElement, scrollElement.childNodes[2]); - } - - moveViewLast(view, scrollElement, childrenLength) { - insertBeforeNode(view, scrollElement, scrollElement.children[childrenLength + 1]); - } - - createTopBufferElement(scrollList, element) { - let tr = document.createElement('tr'); - let buffer = document.createElement('td'); - buffer.setAttribute("style","height: 0px"); - tr.appendChild(buffer); - scrollList.insertBefore(tr, element); - return buffer; - } - - createBottomBufferElement(scrollList, element) { - let tr = document.createElement('tr'); - let buffer = document.createElement('td'); - buffer.setAttribute("style","height: 0px"); - tr.appendChild(buffer); - element.parentNode.insertBefore(tr, element.nextSibling); - return buffer; - } - - removeBufferElements(scrollList, topBuffer, bottomBuffer) { - scrollList.removeChild(topBuffer.parentElement); - scrollList.removeChild(bottomBuffer.parentElement); - } -} - -export class DefaultStrategy { - getScrollList(element) { - return element.parentNode; - } - - getScrollContainer(element) { - return this.getScrollList(element).parentElement; - } - - moveViewFirst(view, scrollElement) { - insertBeforeNode(view, scrollElement, scrollElement.childNodes[2]); - } - - moveViewLast(view, scrollElement, childrenLength) { - insertBeforeNode(view, scrollElement, scrollElement.children[childrenLength + 1]); - } - - createTopBufferElement(scrollList, element) { - let elementName = scrollList.localName === 'ul' ? 'li' : 'div'; - let buffer = document.createElement(elementName); - buffer.setAttribute("style","height: 0px"); - scrollList.insertBefore(buffer, element); - return buffer; - } - - createBottomBufferElement(scrollList, element) { - let elementName = scrollList.localName === 'ul' ? 'li' : 'div'; - let buffer = document.createElement(elementName); - buffer.setAttribute("style","height: 0px"); - element.parentNode.insertBefore(buffer, element.nextSibling); - return buffer; - } - - removeBufferElements(scrollList, topBuffer, bottomBuffer) { - scrollList.removeChild(topBuffer); - scrollList.removeChild(bottomBuffer); - } -} diff --git a/dist/es6/virtual-list.html b/dist/es6/virtual-list.html deleted file mode 100644 index fcba809..0000000 --- a/dist/es6/virtual-list.html +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/dist/es6/virtual-list.js b/dist/es6/virtual-list.js deleted file mode 100644 index 6d06fed..0000000 --- a/dist/es6/virtual-list.js +++ /dev/null @@ -1,9 +0,0 @@ -import {bindable} from 'aurelia-templating'; - -export class VirtualList { - @bindable items - - bind(bindingContext, overrideContext){ - this.$parent = bindingContext; - } -} diff --git a/dist/es6/virtual-repeat-strategy-locator.js b/dist/es6/virtual-repeat-strategy-locator.js deleted file mode 100644 index 025d526..0000000 --- a/dist/es6/virtual-repeat-strategy-locator.js +++ /dev/null @@ -1,12 +0,0 @@ -import {RepeatStrategyLocator} from 'aurelia-templating-resources/repeat-strategy-locator'; -import {ArrayVirtualRepeatStrategy} from './array-virtual-repeat-strategy'; - -export class VirtualRepeatStrategyLocator extends RepeatStrategyLocator { - constructor() { - super(); - this.matchers = []; - this.strategies = []; - - this.addStrategy(items => items instanceof Array, new ArrayVirtualRepeatStrategy()); - } -} diff --git a/dist/es6/virtual-repeat.js b/dist/es6/virtual-repeat.js deleted file mode 100644 index 2a8b92f..0000000 --- a/dist/es6/virtual-repeat.js +++ /dev/null @@ -1,391 +0,0 @@ -import {inject} from 'aurelia-dependency-injection'; -import { - ObserverLocator, - calcSplices, - getChangeRecords, - createOverrideContext -} from 'aurelia-binding'; -import { - BoundViewFactory, - ViewSlot, - TargetInstruction, - customAttribute, - bindable, - templateController -} from 'aurelia-templating'; -import { - updateOverrideContext, - createFullOverrideContext, - updateOverrideContexts, - getItemsSourceExpression, - isOneTime, - unwrapExpression -} from 'aurelia-templating-resources/repeat-utilities'; -import {viewsRequireLifecycle} from 'aurelia-templating-resources/analyze-view-factory'; -import { - calcScrollHeight, - calcOuterHeight, - getNthNode, - moveViewFirst, - moveViewLast -} from './utilities'; -import {VirtualRepeatStrategyLocator} from './virtual-repeat-strategy-locator'; -import {ViewStrategyLocator} from './view-strategy'; - -@customAttribute('virtual-repeat') -@templateController -@inject(Element, BoundViewFactory, TargetInstruction, ViewSlot, ObserverLocator, VirtualRepeatStrategyLocator, ViewStrategyLocator) -export class VirtualRepeat { - _first = 0; - _previousFirst = 0; - _viewsLength = 0; - _lastRebind = 0; - _topBufferHeight = 0; - _bottomBufferHeight = 0; - _bufferSize = 5; - _scrollingDown = false; - _scrollingUp = false; - _switchedDirection = false; - _isAttached = false; - _ticking = false; - - @bindable items - @bindable local - constructor(element, viewFactory, instruction, viewSlot, observerLocator, strategyLocator, viewStrategyLocator){ - this.element = element; - this.viewFactory = viewFactory; - this.instruction = instruction; - this.viewSlot = viewSlot; - this.observerLocator = observerLocator; - this.strategyLocator = strategyLocator; - this.viewStrategyLocator = viewStrategyLocator; - this.local = 'item'; - this.sourceExpression = getItemsSourceExpression(this.instruction, 'virtual-repeat.for'); - this.isOneTime = isOneTime(this.sourceExpression); - this.viewsRequireLifecycle = viewsRequireLifecycle(viewFactory); - } - - attached(){ - this._isAttached = true; - let element = this.element; - this.viewStrategy = this.viewStrategyLocator.getStrategy(element); - this.scrollList = this.viewStrategy.getScrollList(element); - this.scrollContainer = this.viewStrategy.getScrollContainer(element); - this.topBuffer = this.viewStrategy.createTopBufferElement(this.scrollList, element); - this.bottomBuffer = this.viewStrategy.createBottomBufferElement(this.scrollList, element); - this.itemsChanged(); - this.scrollListener = () => this._onScroll(); - this.scrollContainer.addEventListener('scroll', this.scrollListener); - } - - bind(bindingContext, overrideContext){ - this.scope = { bindingContext, overrideContext }; - this._itemsLength = this.items.length; - - // TODO Fix this - window.onresize = () => { this._handleResize(); }; - } - - call(context, changes) { - this[context](this.items, changes); - } - - detached() { - this.scrollContainer.removeEventListener('scroll', this.scrollListener); - this._first = 0; - this._previousFirst = 0; - this._viewsLength = 0; - this._lastRebind = 0; - this._topBufferHeight = 0; - this._bottomBufferHeight = 0; - this._scrollingDown = false; - this._scrollingUp = false; - this._switchedDirection = false; - this._isAttached = false; - this._ticking = false; - this.viewStrategy.removeBufferElements(this.scrollList, this.topBuffer, this.bottomBuffer); - this.isLastIndex = false; - this.scrollList = null; - this.scrollContainer = null; - this.scrollContainerHeight = null; - this.viewSlot.removeAll(true); - if(this.scrollHandler) { - this.scrollHandler.dispose(); - } - this._unsubscribeCollection(); - - } - - itemsChanged() { - this._unsubscribeCollection(); - // still bound? - if (!this.scope) { - return; - } - let items = this.items; - this.strategy = this.strategyLocator.getStrategy(items); - this.strategy.createFirstItem(this); - this._calcInitialHeights(); - if (!this.isOneTime && !this._observeInnerCollection()) { - this._observeCollection(); - } - - this.strategy.instanceChanged(this, items, this._viewsLength); - } - - unbind(){ - this.scope = null; - this.items = null; - this._itemsLength = null; - } - - handleCollectionMutated(collection, changes) { - this._itemsLength = collection.length; - this.strategy.instanceMutated(this, collection, changes); - } - - handleInnerCollectionMutated(collection, changes) { - // guard against source expressions that have observable side-effects that could - // cause an infinite loop- eg a value converter that mutates the source array. - if (this.ignoreMutation) { - return; - } - this.ignoreMutation = true; - let newItems = this.sourceExpression.evaluate(this.scope, this.lookupFunctions); - this.observerLocator.taskQueue.queueMicroTask(() => this.ignoreMutation = false); - - // call itemsChanged... - if (newItems === this.items) { - // call itemsChanged directly. - this.itemsChanged(); - } else { - // call itemsChanged indirectly by assigning the new collection value to - // the items property, which will trigger the self-subscriber to call itemsChanged. - this.items = newItems; - } - } - - _onScroll() { - if(!this._ticking) { - requestAnimationFrame(() => this._handleScroll()); - this._ticking = true; - } - } - - _handleScroll() { - if (!this._isAttached) { - return; - } - let itemHeight = this.itemHeight; - let scrollTop = this.scrollContainer.scrollTop; - this._first = Math.floor(scrollTop / itemHeight); - this._checkScrolling(); - // TODO if and else paths do almost same thing, refactor? - // move views down? - if(this._scrollingDown && (this._hasScrolledDownTheBuffer() || (this._switchedDirection && this._hasScrolledDownTheBufferFromTop()))) { - let viewsToMove = this._first - this._lastRebind; - if(this._switchedDirection) { - viewsToMove = this.isAtTop ? this._first : this._bufferSize - (this._lastRebind - this._first); - } - this.isAtTop = false; - this._lastRebind = this._first; - let movedViewsCount = this._moveViews(viewsToMove); - let adjustHeight = movedViewsCount < viewsToMove ? this._bottomBufferHeight : itemHeight * movedViewsCount; - var test = 0 - this._switchedDirection = false; - this._topBufferHeight = this._topBufferHeight + adjustHeight; - this._bottomBufferHeight = this._bottomBufferHeight - adjustHeight; - if (this._bottomBufferHeight >= 0) { - this._adjustBufferHeights(); - } - // move view up? - } else if (this._scrollingUp && (this._hasScrolledUpTheBuffer() || (this._switchedDirection && this._hasScrolledUpTheBufferFromBottom()))) { - let viewsToMove = this._lastRebind - this._first; - if(this._switchedDirection) { - if(this.isLastIndex) { - viewsToMove = this.items.length - this._first - this.elementsInView; - } else { - viewsToMove = this._bufferSize - (this._first - this._lastRebind); - } - } - this.isLastIndex = false; - this._lastRebind = this._first; - let movedViewsCount = this._moveViews(viewsToMove); - this.movedViewsCount = movedViewsCount; - let adjustHeight = movedViewsCount < viewsToMove ? this._topBufferHeight : itemHeight * movedViewsCount; - this._switchedDirection = false; - this._topBufferHeight = this._topBufferHeight - adjustHeight; - this._bottomBufferHeight = this._bottomBufferHeight + adjustHeight; - if (this._topBufferHeight >= 0) { - this._adjustBufferHeights(); - } - } - this._previousFirst = this._first; - - this._ticking = false; - } - - _handleResize() { - var children = this.viewSlot.children, - childrenLength = children.length, - overrideContext, view, addIndex; - - this.scrollContainerHeight = calcScrollHeight(this.scrollContainer); - this._viewsLength = Math.ceil(this.scrollContainerHeight / this.itemHeight) + 1; - - if(this._viewsLength > childrenLength){ - addIndex = children[childrenLength - 1].overrideContext.$index + 1; - overrideContext = createFullOverrideContext(this, this.items[addIndex], addIndex, this.items.length); - view = this.viewFactory.create(); - view.bind(overrideContext.bindingContext, overrideContext); - this.viewSlot.insert(childrenLength, view); - }else if (this._viewsLength < childrenLength){ - this._viewsLength = childrenLength; - } - } - - _checkScrolling() { - if (this._first > this._previousFirst && (this._bottomBufferHeight > 0 || !this.isLastIndex)) { - if (!this._scrollingDown) { - this._scrollingDown = true; - this._scrollingUp = false; - this._switchedDirection = true; - } else { - this._switchedDirection = false; - } - this._isScrolling = true; - } else if (this._first < this._previousFirst && (this._topBufferHeight >= 0 || !this.isAtTop)) { - if (!this._scrollingUp) { - this._scrollingDown = false; - this._scrollingUp = true; - this._switchedDirection = true; - } else { - this._switchedDirection = false; - } - this._isScrolling = true; - } else { - this._isScrolling = false; - } - } - - _hasScrolledDownTheBuffer() { - let atBottom = (this._first + this._viewsLength) >= this.items.length; - let itemsAddedWhileAtBottom = atBottom && this._first > this._lastRebind; - return this._first - this._lastRebind >= this._bufferSize || itemsAddedWhileAtBottom; - } - - _hasScrolledDownTheBufferFromTop() { - return this._first - this._bufferSize > 0; - } - - _hasScrolledUpTheBuffer() { - return this._lastRebind - this._first >= this._bufferSize; - } - - _hasScrolledUpTheBufferFromBottom() { - return this._first + this._bufferSize < this.items.length; - } - - _adjustBufferHeights() { - this.topBuffer.setAttribute('style', `height: ${this._topBufferHeight}px`); - this.bottomBuffer.setAttribute("style", `height: ${this._bottomBufferHeight}px`); - } - - _unsubscribeCollection() { - if (this.collectionObserver) { - this.collectionObserver.unsubscribe(this.callContext, this); - this.collectionObserver = null; - this.callContext = null; - } - } - - _moveViews(length) { - let getNextIndex = this._scrollingDown ? (index, i) => index + i : (index, i) => index - i; - let isAtFirstOrLastIndex = () => this._scrollingDown ? this.isLastIndex : this.isAtTop; - let viewSlot = this.viewSlot; - let childrenLength = viewSlot.children.length; - let viewIndex = this._scrollingDown ? 0 : childrenLength - 1; - let items = this.items; - let scrollList = this.scrollList; - let index = this._scrollingDown ? this._getIndexOfLastView() + 1 : this._getIndexOfFirstView() - 1; - let i = 0; - while(i < length && !isAtFirstOrLastIndex()) { - let view = viewSlot.children[viewIndex]; - let nextIndex = getNextIndex(index, i); - updateOverrideContext(view.overrideContext, nextIndex, items.length); - view.bindingContext[this.local] = items[nextIndex]; - if(this._scrollingDown) { - viewSlot.children.push(viewSlot.children.shift()); - this.viewStrategy.moveViewLast(view, scrollList, childrenLength); - this.isLastIndex = nextIndex >= items.length - 1; - } else { - viewSlot.children.unshift(viewSlot.children.splice(-1,1)[0]); - this.viewStrategy.moveViewFirst(view, scrollList); - this.isAtTop = nextIndex <= 0; - } - i++; - } - return length - (length - i); - } - - _getIndexOfLastView(){ - let children = this.viewSlot.children; - return children[children.length - 1].overrideContext.$index; - } - - _getIndexOfFirstView(){ - let children = this.viewSlot.children; - return children[0].overrideContext.$index; - } - - _calcInitialHeights() { - if (this._viewsLength > 0 && this._itemsLength == this.items.length) { - return; - } - this._itemsLength = this.items.length; - let listItems = this.scrollList.children; - this.itemHeight = calcOuterHeight(listItems[1]); - this.scrollContainerHeight = calcScrollHeight(this.scrollContainer); - this.elementsInView = Math.ceil(this.scrollContainerHeight / this.itemHeight) + 1; - this._viewsLength = (this.elementsInView * 2) + this._bufferSize; - this._bottomBufferHeight = this.itemHeight * this.items.length - this.itemHeight * this._viewsLength; - this.bottomBuffer.setAttribute("style", `height: ${this._bottomBufferHeight}px`); - this._topBufferHeight = 0; - this.topBuffer.setAttribute("style", `height: ${this._topBufferHeight}px`); - // TODO This will cause scrolling back to top when swapping collection instances that have different lengths - instead should keep the scroll position - this.scrollContainer.scrollTop = 0; - this._first = 0; - } - - _observeInnerCollection() { - let items = this._getInnerCollection(); - let strategy = this.strategyLocator.getStrategy(items); - if (!strategy) { - return false; - } - this.collectionObserver = strategy.getCollectionObserver(this.observerLocator, items); - if (!this.collectionObserver) { - return false; - } - this.callContext = 'handleInnerCollectionMutated'; - this.collectionObserver.subscribe(this.callContext, this); - return true; - } - - _getInnerCollection() { - let expression = unwrapExpression(this.sourceExpression); - if (!expression) { - return null; - } - return expression.evaluate(this.scope, null); - } - - _observeCollection() { - let items = this.items; - this.collectionObserver = this.strategy.getCollectionObserver(this.observerLocator, items); - if (this.collectionObserver) { - this.callContext = 'handleCollectionMutated'; - this.collectionObserver.subscribe(this.callContext, this); - } - } -} diff --git a/dist/system/array-virtual-repeat-strategy.js b/dist/system/array-virtual-repeat-strategy.js index 098f337..eca90da 100644 --- a/dist/system/array-virtual-repeat-strategy.js +++ b/dist/system/array-virtual-repeat-strategy.js @@ -1,36 +1,61 @@ -System.register(['aurelia-templating-resources/array-repeat-strategy', 'aurelia-templating-resources/repeat-utilities'], function (_export) { - 'use strict'; +'use strict'; - var ArrayRepeatStrategy, createFullOverrideContext, updateOverrideContexts, updateOverrideContext, updateOneTimeBinding, ArrayVirtualRepeatStrategy; +System.register(['aurelia-templating-resources/array-repeat-strategy', 'aurelia-templating-resources/repeat-utilities', './utilities'], function (_export, _context) { + var ArrayRepeatStrategy, createFullOverrideContext, updateVirtualOverrideContexts, rebindAndMoveView, getElementDistanceToBottomViewPort, ArrayVirtualRepeatStrategy; - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + function _possibleConstructorReturn(self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } - function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + return call && (typeof call === "object" || typeof call === "function") ? call : self; + } + + function _inherits(subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; + } return { setters: [function (_aureliaTemplatingResourcesArrayRepeatStrategy) { ArrayRepeatStrategy = _aureliaTemplatingResourcesArrayRepeatStrategy.ArrayRepeatStrategy; }, function (_aureliaTemplatingResourcesRepeatUtilities) { createFullOverrideContext = _aureliaTemplatingResourcesRepeatUtilities.createFullOverrideContext; - updateOverrideContexts = _aureliaTemplatingResourcesRepeatUtilities.updateOverrideContexts; - updateOverrideContext = _aureliaTemplatingResourcesRepeatUtilities.updateOverrideContext; - updateOneTimeBinding = _aureliaTemplatingResourcesRepeatUtilities.updateOneTimeBinding; + }, function (_utilities) { + updateVirtualOverrideContexts = _utilities.updateVirtualOverrideContexts; + rebindAndMoveView = _utilities.rebindAndMoveView; + getElementDistanceToBottomViewPort = _utilities.getElementDistanceToBottomViewPort; }], execute: function () { - ArrayVirtualRepeatStrategy = (function (_ArrayRepeatStrategy) { + _export('ArrayVirtualRepeatStrategy', ArrayVirtualRepeatStrategy = function (_ArrayRepeatStrategy) { _inherits(ArrayVirtualRepeatStrategy, _ArrayRepeatStrategy); function ArrayVirtualRepeatStrategy() { _classCallCheck(this, ArrayVirtualRepeatStrategy); - _ArrayRepeatStrategy.apply(this, arguments); + return _possibleConstructorReturn(this, _ArrayRepeatStrategy.apply(this, arguments)); } ArrayVirtualRepeatStrategy.prototype.createFirstItem = function createFirstItem(repeat) { var overrideContext = createFullOverrideContext(repeat, repeat.items[0], 0, 1); - var view = repeat.viewFactory.create(); - view.bind(overrideContext.bindingContext, overrideContext); - repeat.viewSlot.add(view); + repeat.addView(overrideContext.bindingContext, overrideContext); }; ArrayVirtualRepeatStrategy.prototype.instanceChanged = function instanceChanged(repeat, items) { @@ -40,26 +65,24 @@ System.register(['aurelia-templating-resources/array-repeat-strategy', 'aurelia- ArrayVirtualRepeatStrategy.prototype._standardProcessInstanceChanged = function _standardProcessInstanceChanged(repeat, items) { for (var i = 1, ii = repeat._viewsLength; i < ii; ++i) { var overrideContext = createFullOverrideContext(repeat, items[i], i, ii); - var view = repeat.viewFactory.create(); - view.bind(overrideContext.bindingContext, overrideContext); - repeat.viewSlot.add(view); + repeat.addView(overrideContext.bindingContext, overrideContext); } }; ArrayVirtualRepeatStrategy.prototype._inPlaceProcessItems = function _inPlaceProcessItems(repeat, items) { var itemsLength = items.length; - var viewsLength = repeat.viewSlot.children.length; + var viewsLength = repeat.viewCount(); var first = repeat._getIndexOfFirstView(); while (viewsLength > repeat._viewsLength) { viewsLength--; - repeat.viewSlot.removeAt(viewsLength, true); + repeat.removeView(viewsLength, true); } var local = repeat.local; for (var i = 0; i < viewsLength; i++) { - var view = repeat.viewSlot.children[i]; + var view = repeat.view(i); var last = i === itemsLength - 1; var middle = i !== 0 && !last; @@ -70,166 +93,195 @@ System.register(['aurelia-templating-resources/array-repeat-strategy', 'aurelia- view.bindingContext[local] = items[i + first]; view.overrideContext.$middle = middle; view.overrideContext.$last = last; - var j = view.bindings.length; - while (j--) { - updateOneTimeBinding(view.bindings[j]); - } - j = view.controllers.length; - while (j--) { - var k = view.controllers[j].boundProperties.length; - while (k--) { - var binding = view.controllers[j].boundProperties[k].binding; - updateOneTimeBinding(binding); - } - } + repeat.updateBindings(view); } - for (var i = viewsLength; i < repeat._viewsLength; i++) { - var overrideContext = createFullOverrideContext(repeat, items[i], i, itemsLength); - var view = repeat.viewFactory.create(); - view.bind(overrideContext.bindingContext, overrideContext); - repeat.viewSlot.add(view); + var minLength = Math.min(repeat._viewsLength, items.length); + for (var _i = viewsLength; _i < minLength; _i++) { + var overrideContext = createFullOverrideContext(repeat, items[_i], _i, itemsLength); + repeat.addView(overrideContext.bindingContext, overrideContext); } }; ArrayVirtualRepeatStrategy.prototype.instanceMutated = function instanceMutated(repeat, array, splices) { - this._updateViews(repeat, repeat.items, splices); + this._standardProcessInstanceMutated(repeat, array, splices); }; ArrayVirtualRepeatStrategy.prototype._standardProcessInstanceMutated = function _standardProcessInstanceMutated(repeat, array, splices) { - var _this = this; + var _this2 = this; + + if (repeat.__queuedSplices) { + for (var i = 0, ii = splices.length; i < ii; ++i) { + var _splices$i = splices[i]; + var index = _splices$i.index; + var removed = _splices$i.removed; + var addedCount = _splices$i.addedCount; + + mergeSplice(repeat.__queuedSplices, index, removed, addedCount); + } + repeat.__array = array.slice(0); + return; + } + + var maybePromise = this._runSplices(repeat, array.slice(0), splices); + if (maybePromise instanceof Promise) { + (function () { + var queuedSplices = repeat.__queuedSplices = []; + + var runQueuedSplices = function runQueuedSplices() { + if (!queuedSplices.length) { + delete repeat.__queuedSplices; + delete repeat.__array; + return; + } + + var nextPromise = _this2._runSplices(repeat, repeat.__array, queuedSplices) || Promise.resolve(); + nextPromise.then(runQueuedSplices); + }; + + maybePromise.then(runQueuedSplices); + })(); + } + }; + + ArrayVirtualRepeatStrategy.prototype._runSplices = function _runSplices(repeat, array, splices) { + var _this3 = this; var removeDelta = 0; - var viewSlot = repeat.viewSlot; var rmPromises = []; for (var i = 0, ii = splices.length; i < ii; ++i) { var splice = splices[i]; var removed = splice.removed; - var viewIndex = this._getViewIndex(repeat, viewSlot, splice.index); - if (viewIndex >= 0) { - for (var j = 0, jj = removed.length; j < jj; ++j) { - var viewOrPromise = viewSlot.removeAt(viewIndex + removeDelta + rmPromises.length, true); - - var _length = viewSlot.children.length; - var overrideContext = createFullOverrideContext(repeat, repeat.items[_length], _length, repeat.items.length); - var view = repeat.viewFactory.create(); - view.bind(overrideContext.bindingContext, overrideContext); - repeat.viewSlot.isAttached = false; - repeat.viewSlot.add(view); - repeat.viewSlot.isAttached = true; - - if (viewOrPromise instanceof Promise) { - rmPromises.push(viewOrPromise); - } + for (var j = 0, jj = removed.length; j < jj; ++j) { + var viewOrPromise = this._removeViewAt(repeat, splice.index + removeDelta + rmPromises.length, true); + if (viewOrPromise instanceof Promise) { + rmPromises.push(viewOrPromise); } - removeDelta -= splice.addedCount; } + removeDelta -= splice.addedCount; } if (rmPromises.length > 0) { - Promise.all(rmPromises).then(function () { - _this._handleAddedSplices(repeat, array, splices); - _this._updateViews(repeat, array, splices); + return Promise.all(rmPromises).then(function () { + _this3._handleAddedSplices(repeat, array, splices); + updateVirtualOverrideContexts(repeat, 0); }); - } else { - this._handleAddedSplices(repeat, array, splices); - this._updateViews(repeat, array, splices); } + this._handleAddedSplices(repeat, array, splices); + updateVirtualOverrideContexts(repeat, 0); }; - ArrayVirtualRepeatStrategy.prototype._updateViews = function _updateViews(repeat, items, splices) { - var totalAdded = 0; - var totalRemoved = 0; - repeat.items = items; - - for (var i = 0, ii = splices.length; i < ii; ++i) { - var splice = splices[0]; - totalAdded += splice.addedCount; - totalRemoved += splice.removed.length; + ArrayVirtualRepeatStrategy.prototype._removeViewAt = function _removeViewAt(repeat, collectionIndex, returnToCache) { + var viewOrPromise = void 0; + var view = void 0; + var viewSlot = repeat.viewSlot; + var viewCount = repeat.viewCount(); + var viewAddIndex = void 0; + + if (!this._isIndexBeforeViewSlot(repeat, viewSlot, collectionIndex) && !this._isIndexAfterViewSlot(repeat, viewSlot, collectionIndex)) { + var viewIndex = this._getViewIndex(repeat, viewSlot, collectionIndex); + viewOrPromise = repeat.removeView(viewIndex, returnToCache); + if (repeat.items.length > viewCount) { + var collectionAddIndex = void 0; + if (repeat._bottomBufferHeight > repeat.itemHeight) { + viewAddIndex = viewCount; + collectionAddIndex = repeat._getIndexOfLastView() + 1; + repeat._bottomBufferHeight = repeat._bottomBufferHeight - repeat.itemHeight; + } else if (repeat._topBufferHeight > 0) { + viewAddIndex = 0; + collectionAddIndex = repeat._getIndexOfFirstView() - 1; + repeat._topBufferHeight = repeat._topBufferHeight - repeat.itemHeight; + } + var data = repeat.items[collectionAddIndex]; + if (data) { + var overrideContext = createFullOverrideContext(repeat, repeat.items[collectionAddIndex], collectionAddIndex, repeat.items.length); + view = repeat.viewFactory.create(); + view.bind(overrideContext.bindingContext, overrideContext); + } + } else { + return viewOrPromise; + } + } else if (this._isIndexBeforeViewSlot(repeat, viewSlot, collectionIndex)) { + if (repeat._bottomBufferHeight > 0) { + repeat._bottomBufferHeight = repeat._bottomBufferHeight - repeat.itemHeight; + rebindAndMoveView(repeat, repeat.view(0), repeat.view(0).overrideContext.$index, true); + } else { + repeat._topBufferHeight = repeat._topBufferHeight - repeat.itemHeight; + } + } else if (this._isIndexAfterViewSlot(repeat, viewSlot, collectionIndex)) { + repeat._bottomBufferHeight = repeat._bottomBufferHeight - repeat.itemHeight; } - var index = repeat._getIndexOfFirstView() - totalRemoved; - - if (index < 0) { - index = 0; + if (viewOrPromise instanceof Promise) { + viewOrPromise.then(function () { + repeat.viewSlot.insert(viewAddIndex, view); + repeat._adjustBufferHeights(); + }); + return undefined; + } else if (view) { + repeat.viewSlot.insert(viewAddIndex, view); } - var viewSlot = repeat.viewSlot; - - for (var i = 0, ii = viewSlot.children.length; i < ii; ++i) { - var view = viewSlot.children[i]; - var nextIndex = index + i; - var itemsLength = items.length; - if (nextIndex <= itemsLength - 1) { - view.bindingContext[repeat.local] = items[nextIndex]; - updateOverrideContext(view.overrideContext, nextIndex, itemsLength); - } - } + repeat._adjustBufferHeights(); + }; - var bufferDelta = repeat.itemHeight * totalAdded + repeat.itemHeight * -totalRemoved; + ArrayVirtualRepeatStrategy.prototype._isIndexBeforeViewSlot = function _isIndexBeforeViewSlot(repeat, viewSlot, index) { + var viewIndex = this._getViewIndex(repeat, viewSlot, index); + return viewIndex < 0; + }; - if (repeat._bottomBufferHeight + bufferDelta < 0) { - repeat._topBufferHeight = repeat._topBufferHeight + bufferDelta; - } else { - repeat._bottomBufferHeight = repeat._bottomBufferHeight + bufferDelta; - } + ArrayVirtualRepeatStrategy.prototype._isIndexAfterViewSlot = function _isIndexAfterViewSlot(repeat, viewSlot, index) { + var viewIndex = this._getViewIndex(repeat, viewSlot, index); + return viewIndex > repeat._viewsLength - 1; + }; - if (repeat._bottomBufferHeight > 0) { - repeat.isLastIndex = false; + ArrayVirtualRepeatStrategy.prototype._getViewIndex = function _getViewIndex(repeat, viewSlot, index) { + if (repeat.viewCount() === 0) { + return -1; } - repeat._adjustBufferHeights(); + var topBufferItems = repeat._topBufferHeight / repeat.itemHeight; + return index - topBufferItems; }; ArrayVirtualRepeatStrategy.prototype._handleAddedSplices = function _handleAddedSplices(repeat, array, splices) { - var spliceIndexLow = undefined; var arrayLength = array.length; + var viewSlot = repeat.viewSlot; for (var i = 0, ii = splices.length; i < ii; ++i) { var splice = splices[i]; var addIndex = splice.index; var end = splice.index + splice.addedCount; - - if (typeof spliceIndexLow === 'undefined' || spliceIndexLow === null || spliceIndexLow > splice.index) { - spliceIndexLow = addIndex; - } - for (; addIndex < end; ++addIndex) { - var overrideContext = createFullOverrideContext(repeat, array[addIndex], addIndex, arrayLength); - var view = repeat.viewFactory.create(); - view.bind(overrideContext.bindingContext, overrideContext); - repeat.viewSlot.insert(addIndex, view); + var hasDistanceToBottomViewPort = getElementDistanceToBottomViewPort(repeat.bottomBuffer.previousElementSibling) > 0; + if (repeat.viewCount() === 0 || !this._isIndexBeforeViewSlot(repeat, viewSlot, addIndex) && !this._isIndexAfterViewSlot(repeat, viewSlot, addIndex) || hasDistanceToBottomViewPort) { + var overrideContext = createFullOverrideContext(repeat, array[addIndex], addIndex, arrayLength); + repeat.insertView(addIndex, overrideContext.bindingContext, overrideContext); + if (!repeat._hasCalculatedSizes) { + repeat._calcInitialHeights(1); + } else if (repeat.viewCount() > repeat._viewsLength) { + if (hasDistanceToBottomViewPort) { + repeat.removeView(0, true, true); + repeat._topBufferHeight = repeat._topBufferHeight + repeat.itemHeight; + repeat._adjustBufferHeights(); + } else { + repeat.removeView(repeat.viewCount() - 1, true, true); + repeat._bottomBufferHeight = repeat._bottomBufferHeight + repeat.itemHeight; + } + } + } else if (this._isIndexBeforeViewSlot(repeat, viewSlot, addIndex)) { + repeat._topBufferHeight = repeat._topBufferHeight + repeat.itemHeight; + } else if (this._isIndexAfterViewSlot(repeat, viewSlot, addIndex)) { + repeat._bottomBufferHeight = repeat._bottomBufferHeight + repeat.itemHeight; + repeat.isLastIndex = false; + } } } - - return spliceIndexLow; - }; - - ArrayVirtualRepeatStrategy.prototype._isIndexInDom = function _isIndexInDom(viewSlot, index) { - if (viewSlot.children.length === 0) { - return false; - } - - var indexLow = viewSlot.children[0].overrideContext.$index; - var indexHi = viewSlot.children[viewSlot.children.length - 1].overrideContext.$index; - - return index >= indexLow && index <= indexHi; - }; - - ArrayVirtualRepeatStrategy.prototype._getViewIndex = function _getViewIndex(repeat, viewSlot, index) { - if (viewSlot.children.length === 0) { - return -1; - } - var indexLow = viewSlot.children[0].overrideContext.$index; - var viewIndex = index - indexLow; - if (viewIndex > repeat._viewsLength - 1) { - viewIndex = -1; - } - return viewIndex; + repeat._adjustBufferHeights(); }; return ArrayVirtualRepeatStrategy; - })(ArrayRepeatStrategy); + }(ArrayRepeatStrategy)); _export('ArrayVirtualRepeatStrategy', ArrayVirtualRepeatStrategy); } diff --git a/dist/system/aurelia-ui-virtualization.d.ts b/dist/system/aurelia-ui-virtualization.d.ts new file mode 100644 index 0000000..cc50ca3 --- /dev/null +++ b/dist/system/aurelia-ui-virtualization.d.ts @@ -0,0 +1,131 @@ +declare module 'aurelia-ui-virtualization' { + import { + updateOverrideContext, + createFullOverrideContext, + getItemsSourceExpression, + isOneTime, + unwrapExpression, + updateOneTimeBinding + } from 'aurelia-templating-resources/repeat-utilities'; + import { + ArrayRepeatStrategy + } from 'aurelia-templating-resources/array-repeat-strategy'; + import { + RepeatStrategyLocator + } from 'aurelia-templating-resources/repeat-strategy-locator'; + import { + inject + } from 'aurelia-dependency-injection'; + import { + ObserverLocator + } from 'aurelia-binding'; + import { + BoundViewFactory, + ViewSlot, + TargetInstruction, + customAttribute, + bindable, + templateController + } from 'aurelia-templating'; + import { + AbstractRepeater + } from 'aurelia-templating-resources'; + import { + viewsRequireLifecycle + } from 'aurelia-templating-resources/analyze-view-factory'; + export function calcOuterHeight(element: any): any; + export function insertBeforeNode(view: any, bottomBuffer: any): any; + + /** + * Update the override context. + * @param startIndex index in collection where to start updating. + */ + export function updateVirtualOverrideContexts(repeat: any, startIndex: any): any; + export function rebindAndMoveView(repeat: VirtualRepeat, view: View, index: number, moveToBottom: boolean): void; + export function getStyleValue(element: any, style: any): any; + export function getElementDistanceToBottomViewPort(element: any): any; + + /** + * A strategy for repeating a template over an array. + */ + export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy { + + // create first item to calculate the heights + createFirstItem(repeat: any): any; + + /** + * Handle the repeat's collection instance changing. + * @param repeat The repeater instance. + * @param items The new array instance. + */ + instanceChanged(repeat: any, items: any): any; + + /** + * Handle the repeat's collection instance mutating. + * @param repeat The repeat instance. + * @param array The modified array. + * @param splices Records of array changes. + */ + instanceMutated(repeat: any, array: any, splices: any): any; + } + export class ViewStrategyLocator { + getStrategy(element: any): any; + } + export class TableStrategy { + getScrollContainer(element: any): any; + moveViewFirst(view: any, topBuffer: any): any; + moveViewLast(view: any, bottomBuffer: any): any; + createTopBufferElement(element: any): any; + createBottomBufferElement(element: any): any; + removeBufferElements(element: any, topBuffer: any, bottomBuffer: any): any; + } + export class DefaultStrategy { + getScrollContainer(element: any): any; + moveViewFirst(view: any, topBuffer: any): any; + moveViewLast(view: any, bottomBuffer: any): any; + createTopBufferElement(element: any): any; + createBottomBufferElement(element: any): any; + removeBufferElements(element: any, topBuffer: any, bottomBuffer: any): any; + } + export class VirtualRepeatStrategyLocator extends RepeatStrategyLocator { + constructor(); + } + export class VirtualRepeat extends AbstractRepeater { + _first: any; + _previousFirst: any; + _viewsLength: any; + _lastRebind: any; + _topBufferHeight: any; + _bottomBufferHeight: any; + _bufferSize: any; + _scrollingDown: any; + _scrollingUp: any; + _switchedDirection: any; + _isAttached: any; + _ticking: any; + _fixedHeightContainer: any; + _hasCalculatedSizes: any; + _isAtTop: any; + items: any; + local: any; + constructor(element: any, viewFactory: any, instruction: any, viewSlot: any, observerLocator: any, strategyLocator: any, viewStrategyLocator: any); + attached(): any; + bind(bindingContext: any, overrideContext: any): any; + call(context: any, changes: any): any; + detached(): any; + itemsChanged(): any; + unbind(): any; + handleCollectionMutated(collection: any, changes: any): any; + handleInnerCollectionMutated(collection: any, changes: any): any; + + // @override AbstractRepeater + viewCount(): any; + views(): any; + view(index: any): any; + addView(bindingContext: any, overrideContext: any): any; + insertView(index: any, bindingContext: any, overrideContext: any): any; + removeAllViews(returnToCache: any, skipAnimation: any): any; + removeView(index: any, returnToCache: any, skipAnimation: any): any; + updateBindings(view: View): any; + } +} \ No newline at end of file diff --git a/dist/system/index.js b/dist/system/index.js index 0c9438f..a396a21 100644 --- a/dist/system/index.js +++ b/dist/system/index.js @@ -1,24 +1,19 @@ -System.register(['./virtual-repeat', './virtual-list'], function (_export) { - 'use strict'; - - var VirtualRepeat, VirtualList; - - _export('configure', configure); - - function configure(config) { - config.globalResources('./virtual-repeat', './virtual-list'); - } +'use strict'; +System.register(['./virtual-repeat'], function (_export, _context) { + var VirtualRepeat; return { setters: [function (_virtualRepeat) { VirtualRepeat = _virtualRepeat.VirtualRepeat; - }, function (_virtualList) { - VirtualList = _virtualList.VirtualList; }], execute: function () { - _export('VirtualRepeat', VirtualRepeat); + function configure(config) { + config.globalResources('./virtual-repeat'); + } + + _export('configure', configure); - _export('VirtualList', VirtualList); + _export('VirtualRepeat', VirtualRepeat); } }; }); \ No newline at end of file diff --git a/dist/system/utilities.js b/dist/system/utilities.js index a90c6dc..7a8f459 100644 --- a/dist/system/utilities.js +++ b/dist/system/utilities.js @@ -1,46 +1,84 @@ -System.register([], function (_export) { - 'use strict'; - - _export('calcOuterHeight', calcOuterHeight); - - _export('calcScrollHeight', calcScrollHeight); - - _export('insertBeforeNode', insertBeforeNode); - - function calcOuterHeight(element) { - var height; - height = element.getBoundingClientRect().height; - height += getStyleValue(element, 'marginTop'); - height += getStyleValue(element, 'marginBottom'); - return height; - } - - function calcScrollHeight(element) { - var height; - height = element.getBoundingClientRect().height; - height -= getStyleValue(element, 'borderTopWidth'); - height -= getStyleValue(element, 'borderBottomWidth'); - return height; - } - - function insertBeforeNode(view, scrollView, node) { - var viewStart = view.firstChild; - var element = viewStart.nextSibling; - var viewEnd = view.lastChild; - - scrollView.insertBefore(viewEnd, node); - scrollView.insertBefore(element, viewEnd); - scrollView.insertBefore(viewStart, element); - } - - function getStyleValue(element, style) { - var currentStyle, styleValue; - currentStyle = element.currentStyle || window.getComputedStyle(element); - styleValue = parseInt(currentStyle[style]); - return Number.isNaN(styleValue) ? 0 : styleValue; - } +'use strict'; + +System.register(['aurelia-templating-resources/repeat-utilities'], function (_export, _context) { + var updateOverrideContext; return { - setters: [], - execute: function () {} + setters: [function (_aureliaTemplatingResourcesRepeatUtilities) { + updateOverrideContext = _aureliaTemplatingResourcesRepeatUtilities.updateOverrideContext; + }], + execute: function () { + function calcOuterHeight(element) { + var height = void 0; + height = element.getBoundingClientRect().height; + height += getStyleValue(element, 'marginTop'); + height += getStyleValue(element, 'marginBottom'); + return height; + } + + _export('calcOuterHeight', calcOuterHeight); + + function insertBeforeNode(view, bottomBuffer) { + var viewStart = view.firstChild; + var element = viewStart.nextSibling; + var viewEnd = view.lastChild; + var parentElement = bottomBuffer.parentElement; + + parentElement.insertBefore(viewEnd, bottomBuffer); + parentElement.insertBefore(element, viewEnd); + parentElement.insertBefore(viewStart, element); + } + + _export('insertBeforeNode', insertBeforeNode); + + function updateVirtualOverrideContexts(repeat, startIndex) { + var views = repeat.viewSlot.children; + var viewLength = views.length; + var collectionLength = repeat.items.length; + + if (startIndex > 0) { + startIndex = startIndex - 1; + } + + var delta = repeat._topBufferHeight / repeat.itemHeight; + + for (; startIndex < viewLength; ++startIndex) { + updateOverrideContext(views[startIndex].overrideContext, startIndex + delta, collectionLength); + } + } + + _export('updateVirtualOverrideContexts', updateVirtualOverrideContexts); + + function rebindAndMoveView(repeat, view, index, moveToBottom) { + var items = repeat.items; + var viewSlot = repeat.viewSlot; + updateOverrideContext(view.overrideContext, index, items.length); + view.bindingContext[repeat.local] = items[index]; + if (moveToBottom) { + viewSlot.children.push(viewSlot.children.shift()); + repeat.viewStrategy.moveViewLast(view, repeat.bottomBuffer); + } else { + viewSlot.children.unshift(viewSlot.children.splice(-1, 1)[0]); + repeat.viewStrategy.moveViewFirst(view, repeat.topBuffer); + } + } + + _export('rebindAndMoveView', rebindAndMoveView); + + function getStyleValue(element, style) { + var currentStyle = void 0; + var styleValue = void 0; + currentStyle = element.currentStyle || window.getComputedStyle(element); + styleValue = parseInt(currentStyle[style], 10); + return Number.isNaN(styleValue) ? 0 : styleValue; + } + + _export('getStyleValue', getStyleValue); + + function getElementDistanceToBottomViewPort(element) { + return document.documentElement.clientHeight - element.getBoundingClientRect().bottom; + } + + _export('getElementDistanceToBottomViewPort', getElementDistanceToBottomViewPort); + } }; }); \ No newline at end of file diff --git a/dist/system/view-strategy.js b/dist/system/view-strategy.js index b1e6f2d..c6e575c 100644 --- a/dist/system/view-strategy.js +++ b/dist/system/view-strategy.js @@ -1,16 +1,20 @@ -System.register(['./utilities'], function (_export) { - 'use strict'; +'use strict'; +System.register(['./utilities'], function (_export, _context) { var insertBeforeNode, ViewStrategyLocator, TableStrategy, DefaultStrategy; - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } return { setters: [function (_utilities) { insertBeforeNode = _utilities.insertBeforeNode; }], execute: function () { - ViewStrategyLocator = (function () { + _export('ViewStrategyLocator', ViewStrategyLocator = function () { function ViewStrategyLocator() { _classCallCheck(this, ViewStrategyLocator); } @@ -18,109 +22,102 @@ System.register(['./utilities'], function (_export) { ViewStrategyLocator.prototype.getStrategy = function getStrategy(element) { if (element.parentNode.localName === 'tbody') { return new TableStrategy(); - } else { - return new DefaultStrategy(); } + return new DefaultStrategy(); }; return ViewStrategyLocator; - })(); + }()); _export('ViewStrategyLocator', ViewStrategyLocator); - TableStrategy = (function () { + _export('TableStrategy', TableStrategy = function () { function TableStrategy() { _classCallCheck(this, TableStrategy); } - TableStrategy.prototype.getScrollList = function getScrollList(element) { - return element.parentNode; - }; - TableStrategy.prototype.getScrollContainer = function getScrollContainer(element) { - return this.getScrollList(element).parentElement.parentElement; + return element.parentNode; }; - TableStrategy.prototype.moveViewFirst = function moveViewFirst(view, scrollElement) { - insertBeforeNode(view, scrollElement, scrollElement.childNodes[2]); + TableStrategy.prototype.moveViewFirst = function moveViewFirst(view, topBuffer) { + insertBeforeNode(view, topBuffer.parentElement.nextElementSibling.previousSibling); }; - TableStrategy.prototype.moveViewLast = function moveViewLast(view, scrollElement, childrenLength) { - insertBeforeNode(view, scrollElement, scrollElement.children[childrenLength + 1]); + TableStrategy.prototype.moveViewLast = function moveViewLast(view, bottomBuffer) { + insertBeforeNode(view, bottomBuffer.parentElement); }; - TableStrategy.prototype.createTopBufferElement = function createTopBufferElement(scrollList, element) { + TableStrategy.prototype.createTopBufferElement = function createTopBufferElement(element) { var tr = document.createElement('tr'); var buffer = document.createElement('td'); - buffer.setAttribute("style", "height: 0px"); + buffer.setAttribute('style', 'height: 0px'); tr.appendChild(buffer); - scrollList.insertBefore(tr, element); + element.parentElement.insertBefore(tr, element); return buffer; }; - TableStrategy.prototype.createBottomBufferElement = function createBottomBufferElement(scrollList, element) { + TableStrategy.prototype.createBottomBufferElement = function createBottomBufferElement(element) { var tr = document.createElement('tr'); var buffer = document.createElement('td'); - buffer.setAttribute("style", "height: 0px"); + buffer.setAttribute('style', 'height: 0px'); tr.appendChild(buffer); element.parentNode.insertBefore(tr, element.nextSibling); return buffer; }; - TableStrategy.prototype.removeBufferElements = function removeBufferElements(scrollList, topBuffer, bottomBuffer) { - scrollList.removeChild(topBuffer.parentElement); - scrollList.removeChild(bottomBuffer.parentElement); + TableStrategy.prototype.removeBufferElements = function removeBufferElements(element, topBuffer, bottomBuffer) { + element.parentElement.removeChild(topBuffer.parentElement); + element.parentElement.removeChild(bottomBuffer.parentElement); }; return TableStrategy; - })(); + }()); _export('TableStrategy', TableStrategy); - DefaultStrategy = (function () { + _export('DefaultStrategy', DefaultStrategy = function () { function DefaultStrategy() { _classCallCheck(this, DefaultStrategy); } - DefaultStrategy.prototype.getScrollList = function getScrollList(element) { - return element.parentNode; - }; - DefaultStrategy.prototype.getScrollContainer = function getScrollContainer(element) { - return this.getScrollList(element).parentElement; + return element.parentNode; }; - DefaultStrategy.prototype.moveViewFirst = function moveViewFirst(view, scrollElement) { - insertBeforeNode(view, scrollElement, scrollElement.childNodes[2]); + DefaultStrategy.prototype.moveViewFirst = function moveViewFirst(view, topBuffer) { + insertBeforeNode(view, topBuffer.nextElementSibling.previousSibling); }; - DefaultStrategy.prototype.moveViewLast = function moveViewLast(view, scrollElement, childrenLength) { - insertBeforeNode(view, scrollElement, scrollElement.children[childrenLength + 1]); + DefaultStrategy.prototype.moveViewLast = function moveViewLast(view, bottomBuffer) { + var previousSibling = bottomBuffer.previousSibling; + var referenceNode = previousSibling.nodeType === 8 && previousSibling.data === 'anchor' ? previousSibling : bottomBuffer; + insertBeforeNode(view, referenceNode); }; - DefaultStrategy.prototype.createTopBufferElement = function createTopBufferElement(scrollList, element) { - var elementName = scrollList.localName === 'ul' ? 'li' : 'div'; + DefaultStrategy.prototype.createTopBufferElement = function createTopBufferElement(element) { + var elementName = element.parentElement.localName === 'ul' ? 'li' : 'div'; var buffer = document.createElement(elementName); - buffer.setAttribute("style", "height: 0px"); - scrollList.insertBefore(buffer, element); + buffer.setAttribute('style', 'height: 0px'); + element.parentElement.insertBefore(buffer, element); return buffer; }; - DefaultStrategy.prototype.createBottomBufferElement = function createBottomBufferElement(scrollList, element) { - var elementName = scrollList.localName === 'ul' ? 'li' : 'div'; + DefaultStrategy.prototype.createBottomBufferElement = function createBottomBufferElement(element) { + var elementName = element.parentElement.localName === 'ul' ? 'li' : 'div'; var buffer = document.createElement(elementName); - buffer.setAttribute("style", "height: 0px"); + buffer.setAttribute('style', 'height: 0px'); element.parentNode.insertBefore(buffer, element.nextSibling); return buffer; }; - DefaultStrategy.prototype.removeBufferElements = function removeBufferElements(scrollList, topBuffer, bottomBuffer) { - scrollList.removeChild(topBuffer); - scrollList.removeChild(bottomBuffer); + DefaultStrategy.prototype.removeBufferElements = function removeBufferElements(element, topBuffer, bottomBuffer) { + element.parentElement.removeChild(topBuffer); + element.parentElement.removeChild(bottomBuffer); }; return DefaultStrategy; - })(); + }()); _export('DefaultStrategy', DefaultStrategy); } diff --git a/dist/system/virtual-list.html b/dist/system/virtual-list.html deleted file mode 100644 index fcba809..0000000 --- a/dist/system/virtual-list.html +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/dist/system/virtual-list.js b/dist/system/virtual-list.js deleted file mode 100644 index 7522f60..0000000 --- a/dist/system/virtual-list.js +++ /dev/null @@ -1,43 +0,0 @@ -System.register(['aurelia-templating'], function (_export) { - 'use strict'; - - var bindable, VirtualList; - - var _createDecoratedClass = (function () { function defineProperties(target, descriptors, initializers) { for (var i = 0; i < descriptors.length; i++) { var descriptor = descriptors[i]; var decorators = descriptor.decorators; var key = descriptor.key; delete descriptor.key; delete descriptor.decorators; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor || descriptor.initializer) descriptor.writable = true; if (decorators) { for (var f = 0; f < decorators.length; f++) { var decorator = decorators[f]; if (typeof decorator === 'function') { descriptor = decorator(target, key, descriptor) || descriptor; } else { throw new TypeError('The decorator for method ' + descriptor.key + ' is of the invalid type ' + typeof decorator); } } if (descriptor.initializer !== undefined) { initializers[key] = descriptor; continue; } } Object.defineProperty(target, key, descriptor); } } return function (Constructor, protoProps, staticProps, protoInitializers, staticInitializers) { if (protoProps) defineProperties(Constructor.prototype, protoProps, protoInitializers); if (staticProps) defineProperties(Constructor, staticProps, staticInitializers); return Constructor; }; })(); - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - - function _defineDecoratedPropertyDescriptor(target, key, descriptors) { var _descriptor = descriptors[key]; if (!_descriptor) return; var descriptor = {}; for (var _key in _descriptor) descriptor[_key] = _descriptor[_key]; descriptor.value = descriptor.initializer ? descriptor.initializer.call(target) : undefined; Object.defineProperty(target, key, descriptor); } - - return { - setters: [function (_aureliaTemplating) { - bindable = _aureliaTemplating.bindable; - }], - execute: function () { - VirtualList = (function () { - var _instanceInitializers = {}; - - function VirtualList() { - _classCallCheck(this, VirtualList); - - _defineDecoratedPropertyDescriptor(this, 'items', _instanceInitializers); - } - - VirtualList.prototype.bind = function bind(bindingContext, overrideContext) { - this.$parent = bindingContext; - }; - - _createDecoratedClass(VirtualList, [{ - key: 'items', - decorators: [bindable], - initializer: null, - enumerable: true - }], null, _instanceInitializers); - - return VirtualList; - })(); - - _export('VirtualList', VirtualList); - } - }; -}); \ No newline at end of file diff --git a/dist/system/virtual-repeat-strategy-locator.js b/dist/system/virtual-repeat-strategy-locator.js index d703836..4c04ae3 100644 --- a/dist/system/virtual-repeat-strategy-locator.js +++ b/dist/system/virtual-repeat-strategy-locator.js @@ -1,36 +1,64 @@ -System.register(['aurelia-templating-resources', './array-virtual-repeat-strategy'], function (_export) { - 'use strict'; +'use strict'; +System.register(['aurelia-templating-resources/repeat-strategy-locator', './array-virtual-repeat-strategy'], function (_export, _context) { var RepeatStrategyLocator, ArrayVirtualRepeatStrategy, VirtualRepeatStrategyLocator; - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + function _possibleConstructorReturn(self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } - function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + return call && (typeof call === "object" || typeof call === "function") ? call : self; + } + + function _inherits(subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; + } return { - setters: [function (_aureliaTemplatingResources) { - RepeatStrategyLocator = _aureliaTemplatingResources.RepeatStrategyLocator; + setters: [function (_aureliaTemplatingResourcesRepeatStrategyLocator) { + RepeatStrategyLocator = _aureliaTemplatingResourcesRepeatStrategyLocator.RepeatStrategyLocator; }, function (_arrayVirtualRepeatStrategy) { ArrayVirtualRepeatStrategy = _arrayVirtualRepeatStrategy.ArrayVirtualRepeatStrategy; }], execute: function () { - VirtualRepeatStrategyLocator = (function (_RepeatStrategyLocator) { - _inherits(VirtualRepeatStrategyLocator, _RepeatStrategyLocator); + _export('VirtualRepeatStrategyLocator', VirtualRepeatStrategyLocator = function (_RepeatStrategyLocato) { + _inherits(VirtualRepeatStrategyLocator, _RepeatStrategyLocato); function VirtualRepeatStrategyLocator() { _classCallCheck(this, VirtualRepeatStrategyLocator); - _RepeatStrategyLocator.call(this); - this.matchers = []; - this.strategies = []; + var _this = _possibleConstructorReturn(this, _RepeatStrategyLocato.call(this)); + + _this.matchers = []; + _this.strategies = []; - this.addStrategy(function (items) { + _this.addStrategy(function (items) { return items instanceof Array; }, new ArrayVirtualRepeatStrategy()); + return _this; } return VirtualRepeatStrategyLocator; - })(RepeatStrategyLocator); + }(RepeatStrategyLocator)); _export('VirtualRepeatStrategyLocator', VirtualRepeatStrategyLocator); } diff --git a/dist/system/virtual-repeat.js b/dist/system/virtual-repeat.js index 215ac76..66b1a55 100644 --- a/dist/system/virtual-repeat.js +++ b/dist/system/virtual-repeat.js @@ -1,22 +1,86 @@ -System.register(['aurelia-dependency-injection', 'aurelia-binding', 'aurelia-templating', 'aurelia-templating-resources/repeat-utilities', 'aurelia-templating-resources/analyze-view-factory', './utilities', './virtual-repeat-strategy-locator', './view-strategy'], function (_export) { - 'use strict'; +'use strict'; + +System.register(['aurelia-dependency-injection', 'aurelia-binding', 'aurelia-templating', 'aurelia-templating-resources', 'aurelia-templating-resources/repeat-utilities', 'aurelia-templating-resources/analyze-view-factory', './utilities', './virtual-repeat-strategy-locator', './view-strategy'], function (_export, _context) { + var inject, ObserverLocator, BoundViewFactory, ViewSlot, TargetInstruction, customAttribute, bindable, templateController, AbstractRepeater, getItemsSourceExpression, isOneTime, unwrapExpression, updateOneTimeBinding, viewsRequireLifecycle, getStyleValue, calcOuterHeight, rebindAndMoveView, VirtualRepeatStrategyLocator, ViewStrategyLocator, _dec, _dec2, _class, _desc, _value, _class2, _descriptor, _descriptor2, VirtualRepeat; + + function _initDefineProp(target, property, descriptor, context) { + if (!descriptor) return; + Object.defineProperty(target, property, { + enumerable: descriptor.enumerable, + configurable: descriptor.configurable, + writable: descriptor.writable, + value: descriptor.initializer ? descriptor.initializer.call(context) : void 0 + }); + } + + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + function _possibleConstructorReturn(self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + + return call && (typeof call === "object" || typeof call === "function") ? call : self; + } + + function _inherits(subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; + } + + function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } - var inject, ObserverLocator, calcSplices, getChangeRecords, createOverrideContext, BoundViewFactory, ViewSlot, TargetInstruction, customAttribute, bindable, templateController, updateOverrideContext, createFullOverrideContext, updateOverrideContexts, getItemsSourceExpression, isOneTime, unwrapExpression, viewsRequireLifecycle, calcScrollHeight, calcOuterHeight, getNthNode, moveViewFirst, moveViewLast, VirtualRepeatStrategyLocator, ViewStrategyLocator, VirtualRepeat; + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } - var _createDecoratedClass = (function () { function defineProperties(target, descriptors, initializers) { for (var i = 0; i < descriptors.length; i++) { var descriptor = descriptors[i]; var decorators = descriptor.decorators; var key = descriptor.key; delete descriptor.key; delete descriptor.decorators; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor || descriptor.initializer) descriptor.writable = true; if (decorators) { for (var f = 0; f < decorators.length; f++) { var decorator = decorators[f]; if (typeof decorator === 'function') { descriptor = decorator(target, key, descriptor) || descriptor; } else { throw new TypeError('The decorator for method ' + descriptor.key + ' is of the invalid type ' + typeof decorator); } } if (descriptor.initializer !== undefined) { initializers[key] = descriptor; continue; } } Object.defineProperty(target, key, descriptor); } } return function (Constructor, protoProps, staticProps, protoInitializers, staticInitializers) { if (protoProps) defineProperties(Constructor.prototype, protoProps, protoInitializers); if (staticProps) defineProperties(Constructor, staticProps, staticInitializers); return Constructor; }; })(); + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + return desc; + } - function _defineDecoratedPropertyDescriptor(target, key, descriptors) { var _descriptor = descriptors[key]; if (!_descriptor) return; var descriptor = {}; for (var _key in _descriptor) descriptor[_key] = _descriptor[_key]; descriptor.value = descriptor.initializer ? descriptor.initializer.call(target) : undefined; Object.defineProperty(target, key, descriptor); } + function _initializerWarningHelper(descriptor, context) { + throw new Error('Decorating class property failed. Please ensure that transform-class-properties is enabled.'); + } return { setters: [function (_aureliaDependencyInjection) { inject = _aureliaDependencyInjection.inject; }, function (_aureliaBinding) { ObserverLocator = _aureliaBinding.ObserverLocator; - calcSplices = _aureliaBinding.calcSplices; - getChangeRecords = _aureliaBinding.getChangeRecords; - createOverrideContext = _aureliaBinding.createOverrideContext; }, function (_aureliaTemplating) { BoundViewFactory = _aureliaTemplating.BoundViewFactory; ViewSlot = _aureliaTemplating.ViewSlot; @@ -24,101 +88,93 @@ System.register(['aurelia-dependency-injection', 'aurelia-binding', 'aurelia-tem customAttribute = _aureliaTemplating.customAttribute; bindable = _aureliaTemplating.bindable; templateController = _aureliaTemplating.templateController; + }, function (_aureliaTemplatingResources) { + AbstractRepeater = _aureliaTemplatingResources.AbstractRepeater; }, function (_aureliaTemplatingResourcesRepeatUtilities) { - updateOverrideContext = _aureliaTemplatingResourcesRepeatUtilities.updateOverrideContext; - createFullOverrideContext = _aureliaTemplatingResourcesRepeatUtilities.createFullOverrideContext; - updateOverrideContexts = _aureliaTemplatingResourcesRepeatUtilities.updateOverrideContexts; getItemsSourceExpression = _aureliaTemplatingResourcesRepeatUtilities.getItemsSourceExpression; isOneTime = _aureliaTemplatingResourcesRepeatUtilities.isOneTime; unwrapExpression = _aureliaTemplatingResourcesRepeatUtilities.unwrapExpression; + updateOneTimeBinding = _aureliaTemplatingResourcesRepeatUtilities.updateOneTimeBinding; }, function (_aureliaTemplatingResourcesAnalyzeViewFactory) { viewsRequireLifecycle = _aureliaTemplatingResourcesAnalyzeViewFactory.viewsRequireLifecycle; }, function (_utilities) { - calcScrollHeight = _utilities.calcScrollHeight; + getStyleValue = _utilities.getStyleValue; calcOuterHeight = _utilities.calcOuterHeight; - getNthNode = _utilities.getNthNode; - moveViewFirst = _utilities.moveViewFirst; - moveViewLast = _utilities.moveViewLast; + rebindAndMoveView = _utilities.rebindAndMoveView; }, function (_virtualRepeatStrategyLocator) { VirtualRepeatStrategyLocator = _virtualRepeatStrategyLocator.VirtualRepeatStrategyLocator; }, function (_viewStrategy) { ViewStrategyLocator = _viewStrategy.ViewStrategyLocator; }], execute: function () { - VirtualRepeat = (function () { - var _instanceInitializers = {}; - - _createDecoratedClass(VirtualRepeat, [{ - key: 'items', - decorators: [bindable], - initializer: null, - enumerable: true - }, { - key: 'local', - decorators: [bindable], - initializer: null, - enumerable: true - }], null, _instanceInitializers); + _export('VirtualRepeat', VirtualRepeat = (_dec = customAttribute('virtual-repeat'), _dec2 = inject(Element, BoundViewFactory, TargetInstruction, ViewSlot, ObserverLocator, VirtualRepeatStrategyLocator, ViewStrategyLocator), _dec(_class = templateController(_class = _dec2(_class = (_class2 = function (_AbstractRepeater) { + _inherits(VirtualRepeat, _AbstractRepeater); function VirtualRepeat(element, viewFactory, instruction, viewSlot, observerLocator, strategyLocator, viewStrategyLocator) { - _classCallCheck(this, _VirtualRepeat); - - this._first = 0; - this._previousFirst = 0; - this._viewsLength = 0; - this._lastRebind = 0; - this._topBufferHeight = 0; - this._bottomBufferHeight = 0; - this._bufferSize = 5; - this._scrollingDown = false; - this._scrollingUp = false; - this._switchedDirection = false; - this._isAttached = false; - this._ticking = false; - - _defineDecoratedPropertyDescriptor(this, 'items', _instanceInitializers); - - _defineDecoratedPropertyDescriptor(this, 'local', _instanceInitializers); - - this.element = element; - this.viewFactory = viewFactory; - this.instruction = instruction; - this.viewSlot = viewSlot; - this.observerLocator = observerLocator; - this.strategyLocator = strategyLocator; - this.viewStrategyLocator = viewStrategyLocator; - this.local = 'item'; - this.sourceExpression = getItemsSourceExpression(this.instruction, 'virtual-repeat.for'); - this.isOneTime = isOneTime(this.sourceExpression); - this.viewsRequireLifecycle = viewsRequireLifecycle(viewFactory); + _classCallCheck(this, VirtualRepeat); + + var _this = _possibleConstructorReturn(this, _AbstractRepeater.call(this, { + local: 'item', + viewsRequireLifecycle: viewsRequireLifecycle(viewFactory) + })); + + _this._first = 0; + _this._previousFirst = 0; + _this._viewsLength = 0; + _this._lastRebind = 0; + _this._topBufferHeight = 0; + _this._bottomBufferHeight = 0; + _this._bufferSize = 5; + _this._scrollingDown = false; + _this._scrollingUp = false; + _this._switchedDirection = false; + _this._isAttached = false; + _this._ticking = false; + _this._fixedHeightContainer = false; + _this._hasCalculatedSizes = false; + _this._isAtTop = true; + + _initDefineProp(_this, 'items', _descriptor, _this); + + _initDefineProp(_this, 'local', _descriptor2, _this); + + _this.element = element; + _this.viewFactory = viewFactory; + _this.instruction = instruction; + _this.viewSlot = viewSlot; + _this.observerLocator = observerLocator; + _this.strategyLocator = strategyLocator; + _this.viewStrategyLocator = viewStrategyLocator; + _this.sourceExpression = getItemsSourceExpression(_this.instruction, 'virtual-repeat.for'); + _this.isOneTime = isOneTime(_this.sourceExpression); + return _this; } VirtualRepeat.prototype.attached = function attached() { - var _this = this; + var _this2 = this; this._isAttached = true; var element = this.element; this.viewStrategy = this.viewStrategyLocator.getStrategy(element); - this.scrollList = this.viewStrategy.getScrollList(element); this.scrollContainer = this.viewStrategy.getScrollContainer(element); - this.topBuffer = this.viewStrategy.createTopBufferElement(this.scrollList, element); - this.bottomBuffer = this.viewStrategy.createBottomBufferElement(this.scrollList, element); + this.topBuffer = this.viewStrategy.createTopBufferElement(element); + this.bottomBuffer = this.viewStrategy.createBottomBufferElement(element); this.itemsChanged(); this.scrollListener = function () { - return _this._onScroll(); + return _this2._onScroll(); }; - this.scrollContainer.addEventListener('scroll', this.scrollListener); + var containerStyle = this.scrollContainer.style; + if (containerStyle.overflowY === 'scroll' || containerStyle.overflow === 'scroll' || containerStyle.overflowY === 'auto' || containerStyle.overflow === 'auto') { + this._fixedHeightContainer = true; + this.scrollContainer.addEventListener('scroll', this.scrollListener); + } else { + document.addEventListener('scroll', this.scrollListener); + } }; VirtualRepeat.prototype.bind = function bind(bindingContext, overrideContext) { - var _this2 = this; - this.scope = { bindingContext: bindingContext, overrideContext: overrideContext }; this._itemsLength = this.items.length; - - window.onresize = function () { - _this2._handleResize(); - }; }; VirtualRepeat.prototype.call = function call(context, changes) { @@ -138,12 +194,12 @@ System.register(['aurelia-dependency-injection', 'aurelia-binding', 'aurelia-tem this._switchedDirection = false; this._isAttached = false; this._ticking = false; - this.viewStrategy.removeBufferElements(this.scrollList, this.topBuffer, this.bottomBuffer); + this._hasCalculatedSizes = false; + this.viewStrategy.removeBufferElements(this.element, this.topBuffer, this.bottomBuffer); this.isLastIndex = false; - this.scrollList = null; this.scrollContainer = null; this.scrollContainerHeight = null; - this.viewSlot.removeAll(true); + this.removeAllViews(true); if (this.scrollHandler) { this.scrollHandler.dispose(); } @@ -158,8 +214,10 @@ System.register(['aurelia-dependency-injection', 'aurelia-binding', 'aurelia-tem } var items = this.items; this.strategy = this.strategyLocator.getStrategy(items); - this.strategy.createFirstItem(this); - this._calcInitialHeights(); + if (items.length > 0) { + this.strategy.createFirstItem(this); + } + this._calcInitialHeights(items.length); if (!this.isOneTime && !this._observeInnerCollection()) { this._observeCollection(); } @@ -174,6 +232,7 @@ System.register(['aurelia-dependency-injection', 'aurelia-binding', 'aurelia-tem }; VirtualRepeat.prototype.handleCollectionMutated = function handleCollectionMutated(collection, changes) { + this._handlingMutations = true; this._itemsLength = collection.length; this.strategy.instanceMutated(this, collection, changes); }; @@ -200,12 +259,16 @@ System.register(['aurelia-dependency-injection', 'aurelia-binding', 'aurelia-tem VirtualRepeat.prototype._onScroll = function _onScroll() { var _this4 = this; - if (!this._ticking) { + if (!this._ticking && !this._handlingMutations) { requestAnimationFrame(function () { return _this4._handleScroll(); }); this._ticking = true; } + + if (this._handlingMutations) { + this._handlingMutations = false; + } }; VirtualRepeat.prototype._handleScroll = function _handleScroll() { @@ -213,73 +276,55 @@ System.register(['aurelia-dependency-injection', 'aurelia-binding', 'aurelia-tem return; } var itemHeight = this.itemHeight; - var scrollTop = this.scrollContainer.scrollTop; + var scrollTop = this._fixedHeightContainer ? this.scrollContainer.scrollTop : pageYOffset - this.topBuffer.offsetTop; this._first = Math.floor(scrollTop / itemHeight); + this._first = this._first < 0 ? 0 : this._first; + if (this._first > this.items.length - this.elementsInView) { + this._first = this.items.length - this.elementsInView; + } this._checkScrolling(); - if (this._scrollingDown && (this._hasScrolledDownTheBuffer() || this._switchedDirection && this._hasScrolledDownTheBufferFromTop())) { + if (this._scrollingDown) { var viewsToMove = this._first - this._lastRebind; if (this._switchedDirection) { - viewsToMove = this.isAtTop ? this._first : this._bufferSize - (this._lastRebind - this._first); + viewsToMove = this._isAtTop ? this._first : this._bufferSize - (this._lastRebind - this._first); } - this.isAtTop = false; + this._isAtTop = false; this._lastRebind = this._first; var movedViewsCount = this._moveViews(viewsToMove); var adjustHeight = movedViewsCount < viewsToMove ? this._bottomBufferHeight : itemHeight * movedViewsCount; - var test = 0; this._switchedDirection = false; this._topBufferHeight = this._topBufferHeight + adjustHeight; this._bottomBufferHeight = this._bottomBufferHeight - adjustHeight; if (this._bottomBufferHeight >= 0) { this._adjustBufferHeights(); } - } else if (this._scrollingUp && (this._hasScrolledUpTheBuffer() || this._switchedDirection && this._hasScrolledUpTheBufferFromBottom())) { - var viewsToMove = this._lastRebind - this._first; - if (this._switchedDirection) { - if (this.isLastIndex) { - viewsToMove = this.items.length - this._first - this.elementsInView; - } else { - viewsToMove = this._bufferSize - (this._first - this._lastRebind); - } - } - this.isLastIndex = false; - this._lastRebind = this._first; - var movedViewsCount = this._moveViews(viewsToMove); - this.movedViewsCount = movedViewsCount; - var adjustHeight = movedViewsCount < viewsToMove ? this._topBufferHeight : itemHeight * movedViewsCount; - this._switchedDirection = false; - this._topBufferHeight = this._topBufferHeight - adjustHeight; - this._bottomBufferHeight = this._bottomBufferHeight + adjustHeight; - if (this._topBufferHeight >= 0) { - this._adjustBufferHeights(); + } else if (this._scrollingUp) { + var _viewsToMove = this._lastRebind - this._first; + if (this._switchedDirection) { + if (this.isLastIndex) { + _viewsToMove = this.items.length - this._first - this.elementsInView; + } else { + _viewsToMove = this._bufferSize - (this._first - this._lastRebind); } } + this.isLastIndex = false; + this._lastRebind = this._first; + var _movedViewsCount = this._moveViews(_viewsToMove); + this.movedViewsCount = _movedViewsCount; + var _adjustHeight = _movedViewsCount < _viewsToMove ? this._topBufferHeight : itemHeight * _movedViewsCount; + this._switchedDirection = false; + this._topBufferHeight = this._topBufferHeight - _adjustHeight; + this._bottomBufferHeight = this._bottomBufferHeight + _adjustHeight; + if (this._topBufferHeight >= 0) { + this._adjustBufferHeights(); + } + } this._previousFirst = this._first; this._ticking = false; }; - VirtualRepeat.prototype._handleResize = function _handleResize() { - var children = this.viewSlot.children, - childrenLength = children.length, - overrideContext, - view, - addIndex; - - this.scrollContainerHeight = calcScrollHeight(this.scrollContainer); - this._viewsLength = Math.ceil(this.scrollContainerHeight / this.itemHeight) + 1; - - if (this._viewsLength > childrenLength) { - addIndex = children[childrenLength - 1].overrideContext.$index + 1; - overrideContext = createFullOverrideContext(this, this.items[addIndex], addIndex, this.items.length); - view = this.viewFactory.create(); - view.bind(overrideContext.bindingContext, overrideContext); - this.viewSlot.insert(childrenLength, view); - } else if (this._viewsLength < childrenLength) { - this._viewsLength = childrenLength; - } - }; - VirtualRepeat.prototype._checkScrolling = function _checkScrolling() { if (this._first > this._previousFirst && (this._bottomBufferHeight > 0 || !this.isLastIndex)) { if (!this._scrollingDown) { @@ -290,7 +335,7 @@ System.register(['aurelia-dependency-injection', 'aurelia-binding', 'aurelia-tem this._switchedDirection = false; } this._isScrolling = true; - } else if (this._first < this._previousFirst && (this._topBufferHeight >= 0 || !this.isAtTop)) { + } else if (this._first < this._previousFirst && (this._topBufferHeight >= 0 || !this._isAtTop)) { if (!this._scrollingUp) { this._scrollingDown = false; this._scrollingUp = true; @@ -304,27 +349,9 @@ System.register(['aurelia-dependency-injection', 'aurelia-binding', 'aurelia-tem } }; - VirtualRepeat.prototype._hasScrolledDownTheBuffer = function _hasScrolledDownTheBuffer() { - var atBottom = this._first + this._viewsLength >= this.items.length; - var itemsAddedWhileAtBottom = atBottom && this._first > this._lastRebind; - return this._first - this._lastRebind >= this._bufferSize || itemsAddedWhileAtBottom; - }; - - VirtualRepeat.prototype._hasScrolledDownTheBufferFromTop = function _hasScrolledDownTheBufferFromTop() { - return this._first - this._bufferSize > 0; - }; - - VirtualRepeat.prototype._hasScrolledUpTheBuffer = function _hasScrolledUpTheBuffer() { - return this._lastRebind - this._first >= this._bufferSize; - }; - - VirtualRepeat.prototype._hasScrolledUpTheBufferFromBottom = function _hasScrolledUpTheBufferFromBottom() { - return this._first + this._bufferSize < this.items.length; - }; - VirtualRepeat.prototype._adjustBufferHeights = function _adjustBufferHeights() { this.topBuffer.setAttribute('style', 'height: ' + this._topBufferHeight + 'px'); - this.bottomBuffer.setAttribute("style", 'height: ' + this._bottomBufferHeight + 'px'); + this.bottomBuffer.setAttribute('style', 'height: ' + this._bottomBufferHeight + 'px'); }; VirtualRepeat.prototype._unsubscribeCollection = function _unsubscribeCollection() { @@ -344,63 +371,69 @@ System.register(['aurelia-dependency-injection', 'aurelia-binding', 'aurelia-tem return index - i; }; var isAtFirstOrLastIndex = function isAtFirstOrLastIndex() { - return _this5._scrollingDown ? _this5.isLastIndex : _this5.isAtTop; + return _this5._scrollingDown ? _this5.isLastIndex : _this5._isAtTop; }; - var viewSlot = this.viewSlot; - var childrenLength = viewSlot.children.length; + var childrenLength = this.viewCount(); var viewIndex = this._scrollingDown ? 0 : childrenLength - 1; var items = this.items; - var scrollList = this.scrollList; var index = this._scrollingDown ? this._getIndexOfLastView() + 1 : this._getIndexOfFirstView() - 1; var i = 0; while (i < length && !isAtFirstOrLastIndex()) { - var view = viewSlot.children[viewIndex]; + var view = this.view(viewIndex); var nextIndex = getNextIndex(index, i); - updateOverrideContext(view.overrideContext, nextIndex, items.length); - view.bindingContext[this.local] = items[nextIndex]; - if (this._scrollingDown) { - viewSlot.children.push(viewSlot.children.shift()); - this.viewStrategy.moveViewLast(view, scrollList, childrenLength); - this.isLastIndex = nextIndex >= items.length - 1; - } else { - viewSlot.children.unshift(viewSlot.children.splice(-1, 1)[0]); - this.viewStrategy.moveViewFirst(view, scrollList); - this.isAtTop = nextIndex <= 0; + this.isLastIndex = nextIndex >= items.length - 1; + this._isAtTop = nextIndex <= 0; + if (!(isAtFirstOrLastIndex() && childrenLength >= items.length)) { + rebindAndMoveView(this, view, nextIndex, this._scrollingDown); + i++; } - i++; } + return length - (length - i); }; VirtualRepeat.prototype._getIndexOfLastView = function _getIndexOfLastView() { - var children = this.viewSlot.children; - return children[children.length - 1].overrideContext.$index; + return this.view(this.viewCount() - 1).overrideContext.$index; }; VirtualRepeat.prototype._getIndexOfFirstView = function _getIndexOfFirstView() { - var children = this.viewSlot.children; - return children[0].overrideContext.$index; + return this.view(0) ? this.view(0).overrideContext.$index : -1; }; - VirtualRepeat.prototype._calcInitialHeights = function _calcInitialHeights() { - if (this._viewsLength > 0 && this._itemsLength == this.items.length) { + VirtualRepeat.prototype._calcInitialHeights = function _calcInitialHeights(itemsLength) { + if (this._viewsLength > 0 && this._itemsLength === itemsLength || itemsLength <= 0) { return; } - this._itemsLength = this.items.length; - var listItems = this.scrollList.children; - this.itemHeight = calcOuterHeight(listItems[1]); - this.scrollContainerHeight = calcScrollHeight(this.scrollContainer); + this._hasCalculatedSizes = true; + this._itemsLength = itemsLength; + var firstViewElement = this.view(0).firstChild.nextElementSibling; + this.itemHeight = calcOuterHeight(firstViewElement); + if (this.itemHeight <= 0) { + throw new Error('Could not calculate item height'); + } + this.scrollContainerHeight = this._fixedHeightContainer ? this._calcScrollHeight(this.scrollContainer) : document.documentElement.clientHeight; this.elementsInView = Math.ceil(this.scrollContainerHeight / this.itemHeight) + 1; this._viewsLength = this.elementsInView * 2 + this._bufferSize; - this._bottomBufferHeight = this.itemHeight * this.items.length - this.itemHeight * this._viewsLength; - this.bottomBuffer.setAttribute("style", 'height: ' + this._bottomBufferHeight + 'px'); + this._bottomBufferHeight = this.itemHeight * itemsLength - this.itemHeight * this._viewsLength; + if (this._bottomBufferHeight < 0) { + this._bottomBufferHeight = 0; + } + this.bottomBuffer.setAttribute('style', 'height: ' + this._bottomBufferHeight + 'px'); this._topBufferHeight = 0; - this.topBuffer.setAttribute("style", 'height: ' + this._topBufferHeight + 'px'); + this.topBuffer.setAttribute('style', 'height: ' + this._topBufferHeight + 'px'); this.scrollContainer.scrollTop = 0; this._first = 0; }; + VirtualRepeat.prototype._calcScrollHeight = function _calcScrollHeight(element) { + var height = void 0; + height = element.getBoundingClientRect().height; + height -= getStyleValue(element, 'borderTopWidth'); + height -= getStyleValue(element, 'borderBottomWidth'); + return height; + }; + VirtualRepeat.prototype._observeInnerCollection = function _observeInnerCollection() { var items = this._getInnerCollection(); var strategy = this.strategyLocator.getStrategy(items); @@ -433,12 +466,61 @@ System.register(['aurelia-dependency-injection', 'aurelia-binding', 'aurelia-tem } }; - var _VirtualRepeat = VirtualRepeat; - VirtualRepeat = inject(Element, BoundViewFactory, TargetInstruction, ViewSlot, ObserverLocator, VirtualRepeatStrategyLocator, ViewStrategyLocator)(VirtualRepeat) || VirtualRepeat; - VirtualRepeat = templateController(VirtualRepeat) || VirtualRepeat; - VirtualRepeat = customAttribute('virtual-repeat')(VirtualRepeat) || VirtualRepeat; + VirtualRepeat.prototype.viewCount = function viewCount() { + return this.viewSlot.children.length; + }; + + VirtualRepeat.prototype.views = function views() { + return this.viewSlot.children; + }; + + VirtualRepeat.prototype.view = function view(index) { + return this.viewSlot.children[index]; + }; + + VirtualRepeat.prototype.addView = function addView(bindingContext, overrideContext) { + var view = this.viewFactory.create(); + view.bind(bindingContext, overrideContext); + this.viewSlot.add(view); + }; + + VirtualRepeat.prototype.insertView = function insertView(index, bindingContext, overrideContext) { + var view = this.viewFactory.create(); + view.bind(bindingContext, overrideContext); + this.viewSlot.insert(index, view); + }; + + VirtualRepeat.prototype.removeAllViews = function removeAllViews(returnToCache, skipAnimation) { + return this.viewSlot.removeAll(returnToCache, skipAnimation); + }; + + VirtualRepeat.prototype.removeView = function removeView(index, returnToCache, skipAnimation) { + return this.viewSlot.removeAt(index, returnToCache, skipAnimation); + }; + + VirtualRepeat.prototype.updateBindings = function updateBindings(view) { + var j = view.bindings.length; + while (j--) { + updateOneTimeBinding(view.bindings[j]); + } + j = view.controllers.length; + while (j--) { + var k = view.controllers[j].boundProperties.length; + while (k--) { + var binding = view.controllers[j].boundProperties[k].binding; + updateOneTimeBinding(binding); + } + } + }; + return VirtualRepeat; - })(); + }(AbstractRepeater), (_descriptor = _applyDecoratedDescriptor(_class2.prototype, 'items', [bindable], { + enumerable: true, + initializer: null + }), _descriptor2 = _applyDecoratedDescriptor(_class2.prototype, 'local', [bindable], { + enumerable: true, + initializer: null + })), _class2)) || _class) || _class) || _class)); _export('VirtualRepeat', VirtualRepeat); } diff --git a/dist/temp/aurelia-ui-virtualization.js b/dist/temp/aurelia-ui-virtualization.js new file mode 100644 index 0000000..5d79c69 --- /dev/null +++ b/dist/temp/aurelia-ui-virtualization.js @@ -0,0 +1,916 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.VirtualRepeat = exports.VirtualRepeatStrategyLocator = exports.DefaultStrategy = exports.TableStrategy = exports.ViewStrategyLocator = exports.ArrayVirtualRepeatStrategy = undefined; + +var _dec, _dec2, _class, _desc, _value, _class2, _descriptor, _descriptor2; + +exports.calcOuterHeight = calcOuterHeight; +exports.insertBeforeNode = insertBeforeNode; +exports.updateVirtualOverrideContexts = updateVirtualOverrideContexts; +exports.rebindAndMoveView = rebindAndMoveView; +exports.getStyleValue = getStyleValue; +exports.getElementDistanceToBottomViewPort = getElementDistanceToBottomViewPort; + +var _repeatUtilities = require('aurelia-templating-resources/repeat-utilities'); + +var _arrayRepeatStrategy = require('aurelia-templating-resources/array-repeat-strategy'); + +var _repeatStrategyLocator = require('aurelia-templating-resources/repeat-strategy-locator'); + +var _aureliaDependencyInjection = require('aurelia-dependency-injection'); + +var _aureliaBinding = require('aurelia-binding'); + +var _aureliaTemplating = require('aurelia-templating'); + +var _aureliaTemplatingResources = require('aurelia-templating-resources'); + +var _analyzeViewFactory = require('aurelia-templating-resources/analyze-view-factory'); + +function _initDefineProp(target, property, descriptor, context) { + if (!descriptor) return; + Object.defineProperty(target, property, { + enumerable: descriptor.enumerable, + configurable: descriptor.configurable, + writable: descriptor.writable, + value: descriptor.initializer ? descriptor.initializer.call(context) : void 0 + }); +} + +function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function (key) { + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + + if ('value' in desc || desc.initializer) { + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce(function (desc, decorator) { + return decorator(target, property, desc) || desc; + }, desc); + + if (context && desc.initializer !== void 0) { + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } + + if (desc.initializer === void 0) { + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; +} + +function _initializerWarningHelper(descriptor, context) { + throw new Error('Decorating class property failed. Please ensure that transform-class-properties is enabled.'); +} + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +function calcOuterHeight(element) { + var height = void 0; + height = element.getBoundingClientRect().height; + height += getStyleValue(element, 'marginTop'); + height += getStyleValue(element, 'marginBottom'); + return height; +} + +function insertBeforeNode(view, bottomBuffer) { + var viewStart = view.firstChild; + var element = viewStart.nextSibling; + var viewEnd = view.lastChild; + var parentElement = bottomBuffer.parentElement; + + parentElement.insertBefore(viewEnd, bottomBuffer); + parentElement.insertBefore(element, viewEnd); + parentElement.insertBefore(viewStart, element); +} + +function updateVirtualOverrideContexts(repeat, startIndex) { + var views = repeat.viewSlot.children; + var viewLength = views.length; + var collectionLength = repeat.items.length; + + if (startIndex > 0) { + startIndex = startIndex - 1; + } + + var delta = repeat._topBufferHeight / repeat.itemHeight; + + for (; startIndex < viewLength; ++startIndex) { + (0, _repeatUtilities.updateOverrideContext)(views[startIndex].overrideContext, startIndex + delta, collectionLength); + } +} + +function rebindAndMoveView(repeat, view, index, moveToBottom) { + var items = repeat.items; + var viewSlot = repeat.viewSlot; + (0, _repeatUtilities.updateOverrideContext)(view.overrideContext, index, items.length); + view.bindingContext[repeat.local] = items[index]; + if (moveToBottom) { + viewSlot.children.push(viewSlot.children.shift()); + repeat.viewStrategy.moveViewLast(view, repeat.bottomBuffer); + } else { + viewSlot.children.unshift(viewSlot.children.splice(-1, 1)[0]); + repeat.viewStrategy.moveViewFirst(view, repeat.topBuffer); + } +} + +function getStyleValue(element, style) { + var currentStyle = void 0; + var styleValue = void 0; + currentStyle = element.currentStyle || window.getComputedStyle(element); + styleValue = parseInt(currentStyle[style], 10); + return Number.isNaN(styleValue) ? 0 : styleValue; +} + +function getElementDistanceToBottomViewPort(element) { + return document.documentElement.clientHeight - element.getBoundingClientRect().bottom; +} + +var ArrayVirtualRepeatStrategy = exports.ArrayVirtualRepeatStrategy = function (_ArrayRepeatStrategy) { + _inherits(ArrayVirtualRepeatStrategy, _ArrayRepeatStrategy); + + function ArrayVirtualRepeatStrategy() { + _classCallCheck(this, ArrayVirtualRepeatStrategy); + + return _possibleConstructorReturn(this, _ArrayRepeatStrategy.apply(this, arguments)); + } + + ArrayVirtualRepeatStrategy.prototype.createFirstItem = function createFirstItem(repeat) { + var overrideContext = (0, _repeatUtilities.createFullOverrideContext)(repeat, repeat.items[0], 0, 1); + repeat.addView(overrideContext.bindingContext, overrideContext); + }; + + ArrayVirtualRepeatStrategy.prototype.instanceChanged = function instanceChanged(repeat, items) { + this._inPlaceProcessItems(repeat, items); + }; + + ArrayVirtualRepeatStrategy.prototype._standardProcessInstanceChanged = function _standardProcessInstanceChanged(repeat, items) { + for (var i = 1, ii = repeat._viewsLength; i < ii; ++i) { + var overrideContext = (0, _repeatUtilities.createFullOverrideContext)(repeat, items[i], i, ii); + repeat.addView(overrideContext.bindingContext, overrideContext); + } + }; + + ArrayVirtualRepeatStrategy.prototype._inPlaceProcessItems = function _inPlaceProcessItems(repeat, items) { + var itemsLength = items.length; + var viewsLength = repeat.viewCount(); + var first = repeat._getIndexOfFirstView(); + + while (viewsLength > repeat._viewsLength) { + viewsLength--; + repeat.removeView(viewsLength, true); + } + + var local = repeat.local; + + for (var i = 0; i < viewsLength; i++) { + var view = repeat.view(i); + var last = i === itemsLength - 1; + var middle = i !== 0 && !last; + + if (view.bindingContext[local] === items[i + first] && view.overrideContext.$middle === middle && view.overrideContext.$last === last) { + continue; + } + + view.bindingContext[local] = items[i + first]; + view.overrideContext.$middle = middle; + view.overrideContext.$last = last; + repeat.updateBindings(view); + } + + var minLength = Math.min(repeat._viewsLength, items.length); + for (var _i = viewsLength; _i < minLength; _i++) { + var overrideContext = (0, _repeatUtilities.createFullOverrideContext)(repeat, items[_i], _i, itemsLength); + repeat.addView(overrideContext.bindingContext, overrideContext); + } + }; + + ArrayVirtualRepeatStrategy.prototype.instanceMutated = function instanceMutated(repeat, array, splices) { + this._standardProcessInstanceMutated(repeat, array, splices); + }; + + ArrayVirtualRepeatStrategy.prototype._standardProcessInstanceMutated = function _standardProcessInstanceMutated(repeat, array, splices) { + var _this2 = this; + + if (repeat.__queuedSplices) { + for (var i = 0, ii = splices.length; i < ii; ++i) { + var _splices$i = splices[i]; + var index = _splices$i.index; + var removed = _splices$i.removed; + var addedCount = _splices$i.addedCount; + + mergeSplice(repeat.__queuedSplices, index, removed, addedCount); + } + repeat.__array = array.slice(0); + return; + } + + var maybePromise = this._runSplices(repeat, array.slice(0), splices); + if (maybePromise instanceof Promise) { + (function () { + var queuedSplices = repeat.__queuedSplices = []; + + var runQueuedSplices = function runQueuedSplices() { + if (!queuedSplices.length) { + delete repeat.__queuedSplices; + delete repeat.__array; + return; + } + + var nextPromise = _this2._runSplices(repeat, repeat.__array, queuedSplices) || Promise.resolve(); + nextPromise.then(runQueuedSplices); + }; + + maybePromise.then(runQueuedSplices); + })(); + } + }; + + ArrayVirtualRepeatStrategy.prototype._runSplices = function _runSplices(repeat, array, splices) { + var _this3 = this; + + var removeDelta = 0; + var rmPromises = []; + + for (var i = 0, ii = splices.length; i < ii; ++i) { + var splice = splices[i]; + var removed = splice.removed; + for (var j = 0, jj = removed.length; j < jj; ++j) { + var viewOrPromise = this._removeViewAt(repeat, splice.index + removeDelta + rmPromises.length, true); + if (viewOrPromise instanceof Promise) { + rmPromises.push(viewOrPromise); + } + } + removeDelta -= splice.addedCount; + } + + if (rmPromises.length > 0) { + return Promise.all(rmPromises).then(function () { + _this3._handleAddedSplices(repeat, array, splices); + updateVirtualOverrideContexts(repeat, 0); + }); + } + this._handleAddedSplices(repeat, array, splices); + updateVirtualOverrideContexts(repeat, 0); + }; + + ArrayVirtualRepeatStrategy.prototype._removeViewAt = function _removeViewAt(repeat, collectionIndex, returnToCache) { + var viewOrPromise = void 0; + var view = void 0; + var viewSlot = repeat.viewSlot; + var viewCount = repeat.viewCount(); + var viewAddIndex = void 0; + + if (!this._isIndexBeforeViewSlot(repeat, viewSlot, collectionIndex) && !this._isIndexAfterViewSlot(repeat, viewSlot, collectionIndex)) { + var viewIndex = this._getViewIndex(repeat, viewSlot, collectionIndex); + viewOrPromise = repeat.removeView(viewIndex, returnToCache); + if (repeat.items.length > viewCount) { + var collectionAddIndex = void 0; + if (repeat._bottomBufferHeight > repeat.itemHeight) { + viewAddIndex = viewCount; + collectionAddIndex = repeat._getIndexOfLastView() + 1; + repeat._bottomBufferHeight = repeat._bottomBufferHeight - repeat.itemHeight; + } else if (repeat._topBufferHeight > 0) { + viewAddIndex = 0; + collectionAddIndex = repeat._getIndexOfFirstView() - 1; + repeat._topBufferHeight = repeat._topBufferHeight - repeat.itemHeight; + } + var data = repeat.items[collectionAddIndex]; + if (data) { + var overrideContext = (0, _repeatUtilities.createFullOverrideContext)(repeat, repeat.items[collectionAddIndex], collectionAddIndex, repeat.items.length); + view = repeat.viewFactory.create(); + view.bind(overrideContext.bindingContext, overrideContext); + } + } else { + return viewOrPromise; + } + } else if (this._isIndexBeforeViewSlot(repeat, viewSlot, collectionIndex)) { + if (repeat._bottomBufferHeight > 0) { + repeat._bottomBufferHeight = repeat._bottomBufferHeight - repeat.itemHeight; + rebindAndMoveView(repeat, repeat.view(0), repeat.view(0).overrideContext.$index, true); + } else { + repeat._topBufferHeight = repeat._topBufferHeight - repeat.itemHeight; + } + } else if (this._isIndexAfterViewSlot(repeat, viewSlot, collectionIndex)) { + repeat._bottomBufferHeight = repeat._bottomBufferHeight - repeat.itemHeight; + } + + if (viewOrPromise instanceof Promise) { + viewOrPromise.then(function () { + repeat.viewSlot.insert(viewAddIndex, view); + repeat._adjustBufferHeights(); + }); + return undefined; + } else if (view) { + repeat.viewSlot.insert(viewAddIndex, view); + } + + repeat._adjustBufferHeights(); + }; + + ArrayVirtualRepeatStrategy.prototype._isIndexBeforeViewSlot = function _isIndexBeforeViewSlot(repeat, viewSlot, index) { + var viewIndex = this._getViewIndex(repeat, viewSlot, index); + return viewIndex < 0; + }; + + ArrayVirtualRepeatStrategy.prototype._isIndexAfterViewSlot = function _isIndexAfterViewSlot(repeat, viewSlot, index) { + var viewIndex = this._getViewIndex(repeat, viewSlot, index); + return viewIndex > repeat._viewsLength - 1; + }; + + ArrayVirtualRepeatStrategy.prototype._getViewIndex = function _getViewIndex(repeat, viewSlot, index) { + if (repeat.viewCount() === 0) { + return -1; + } + + var topBufferItems = repeat._topBufferHeight / repeat.itemHeight; + return index - topBufferItems; + }; + + ArrayVirtualRepeatStrategy.prototype._handleAddedSplices = function _handleAddedSplices(repeat, array, splices) { + var arrayLength = array.length; + var viewSlot = repeat.viewSlot; + for (var i = 0, ii = splices.length; i < ii; ++i) { + var splice = splices[i]; + var addIndex = splice.index; + var end = splice.index + splice.addedCount; + for (; addIndex < end; ++addIndex) { + var hasDistanceToBottomViewPort = getElementDistanceToBottomViewPort(repeat.bottomBuffer.previousElementSibling) > 0; + if (repeat.viewCount() === 0 || !this._isIndexBeforeViewSlot(repeat, viewSlot, addIndex) && !this._isIndexAfterViewSlot(repeat, viewSlot, addIndex) || hasDistanceToBottomViewPort) { + var overrideContext = (0, _repeatUtilities.createFullOverrideContext)(repeat, array[addIndex], addIndex, arrayLength); + repeat.insertView(addIndex, overrideContext.bindingContext, overrideContext); + if (!repeat._hasCalculatedSizes) { + repeat._calcInitialHeights(1); + } else if (repeat.viewCount() > repeat._viewsLength) { + if (hasDistanceToBottomViewPort) { + repeat.removeView(0, true, true); + repeat._topBufferHeight = repeat._topBufferHeight + repeat.itemHeight; + repeat._adjustBufferHeights(); + } else { + repeat.removeView(repeat.viewCount() - 1, true, true); + repeat._bottomBufferHeight = repeat._bottomBufferHeight + repeat.itemHeight; + } + } + } else if (this._isIndexBeforeViewSlot(repeat, viewSlot, addIndex)) { + repeat._topBufferHeight = repeat._topBufferHeight + repeat.itemHeight; + } else if (this._isIndexAfterViewSlot(repeat, viewSlot, addIndex)) { + repeat._bottomBufferHeight = repeat._bottomBufferHeight + repeat.itemHeight; + repeat.isLastIndex = false; + } + } + } + repeat._adjustBufferHeights(); + }; + + return ArrayVirtualRepeatStrategy; +}(_arrayRepeatStrategy.ArrayRepeatStrategy); + +var ViewStrategyLocator = exports.ViewStrategyLocator = function () { + function ViewStrategyLocator() { + _classCallCheck(this, ViewStrategyLocator); + } + + ViewStrategyLocator.prototype.getStrategy = function getStrategy(element) { + if (element.parentNode.localName === 'tbody') { + return new TableStrategy(); + } + return new DefaultStrategy(); + }; + + return ViewStrategyLocator; +}(); + +var TableStrategy = exports.TableStrategy = function () { + function TableStrategy() { + _classCallCheck(this, TableStrategy); + } + + TableStrategy.prototype.getScrollContainer = function getScrollContainer(element) { + return element.parentNode; + }; + + TableStrategy.prototype.moveViewFirst = function moveViewFirst(view, topBuffer) { + insertBeforeNode(view, topBuffer.parentElement.nextElementSibling.previousSibling); + }; + + TableStrategy.prototype.moveViewLast = function moveViewLast(view, bottomBuffer) { + insertBeforeNode(view, bottomBuffer.parentElement); + }; + + TableStrategy.prototype.createTopBufferElement = function createTopBufferElement(element) { + var tr = document.createElement('tr'); + var buffer = document.createElement('td'); + buffer.setAttribute('style', 'height: 0px'); + tr.appendChild(buffer); + element.parentElement.insertBefore(tr, element); + return buffer; + }; + + TableStrategy.prototype.createBottomBufferElement = function createBottomBufferElement(element) { + var tr = document.createElement('tr'); + var buffer = document.createElement('td'); + buffer.setAttribute('style', 'height: 0px'); + tr.appendChild(buffer); + element.parentNode.insertBefore(tr, element.nextSibling); + return buffer; + }; + + TableStrategy.prototype.removeBufferElements = function removeBufferElements(element, topBuffer, bottomBuffer) { + element.parentElement.removeChild(topBuffer.parentElement); + element.parentElement.removeChild(bottomBuffer.parentElement); + }; + + return TableStrategy; +}(); + +var DefaultStrategy = exports.DefaultStrategy = function () { + function DefaultStrategy() { + _classCallCheck(this, DefaultStrategy); + } + + DefaultStrategy.prototype.getScrollContainer = function getScrollContainer(element) { + return element.parentNode; + }; + + DefaultStrategy.prototype.moveViewFirst = function moveViewFirst(view, topBuffer) { + insertBeforeNode(view, topBuffer.nextElementSibling.previousSibling); + }; + + DefaultStrategy.prototype.moveViewLast = function moveViewLast(view, bottomBuffer) { + var previousSibling = bottomBuffer.previousSibling; + var referenceNode = previousSibling.nodeType === 8 && previousSibling.data === 'anchor' ? previousSibling : bottomBuffer; + insertBeforeNode(view, referenceNode); + }; + + DefaultStrategy.prototype.createTopBufferElement = function createTopBufferElement(element) { + var elementName = element.parentElement.localName === 'ul' ? 'li' : 'div'; + var buffer = document.createElement(elementName); + buffer.setAttribute('style', 'height: 0px'); + element.parentElement.insertBefore(buffer, element); + return buffer; + }; + + DefaultStrategy.prototype.createBottomBufferElement = function createBottomBufferElement(element) { + var elementName = element.parentElement.localName === 'ul' ? 'li' : 'div'; + var buffer = document.createElement(elementName); + buffer.setAttribute('style', 'height: 0px'); + element.parentNode.insertBefore(buffer, element.nextSibling); + return buffer; + }; + + DefaultStrategy.prototype.removeBufferElements = function removeBufferElements(element, topBuffer, bottomBuffer) { + element.parentElement.removeChild(topBuffer); + element.parentElement.removeChild(bottomBuffer); + }; + + return DefaultStrategy; +}(); + +var VirtualRepeatStrategyLocator = exports.VirtualRepeatStrategyLocator = function (_RepeatStrategyLocato) { + _inherits(VirtualRepeatStrategyLocator, _RepeatStrategyLocato); + + function VirtualRepeatStrategyLocator() { + _classCallCheck(this, VirtualRepeatStrategyLocator); + + var _this4 = _possibleConstructorReturn(this, _RepeatStrategyLocato.call(this)); + + _this4.matchers = []; + _this4.strategies = []; + + _this4.addStrategy(function (items) { + return items instanceof Array; + }, new ArrayVirtualRepeatStrategy()); + return _this4; + } + + return VirtualRepeatStrategyLocator; +}(_repeatStrategyLocator.RepeatStrategyLocator); + +var VirtualRepeat = exports.VirtualRepeat = (_dec = (0, _aureliaTemplating.customAttribute)('virtual-repeat'), _dec2 = (0, _aureliaDependencyInjection.inject)(Element, _aureliaTemplating.BoundViewFactory, _aureliaTemplating.TargetInstruction, _aureliaTemplating.ViewSlot, _aureliaBinding.ObserverLocator, VirtualRepeatStrategyLocator, ViewStrategyLocator), _dec(_class = (0, _aureliaTemplating.templateController)(_class = _dec2(_class = (_class2 = function (_AbstractRepeater) { + _inherits(VirtualRepeat, _AbstractRepeater); + + function VirtualRepeat(element, viewFactory, instruction, viewSlot, observerLocator, strategyLocator, viewStrategyLocator) { + _classCallCheck(this, VirtualRepeat); + + var _this5 = _possibleConstructorReturn(this, _AbstractRepeater.call(this, { + local: 'item', + viewsRequireLifecycle: (0, _analyzeViewFactory.viewsRequireLifecycle)(viewFactory) + })); + + _this5._first = 0; + _this5._previousFirst = 0; + _this5._viewsLength = 0; + _this5._lastRebind = 0; + _this5._topBufferHeight = 0; + _this5._bottomBufferHeight = 0; + _this5._bufferSize = 5; + _this5._scrollingDown = false; + _this5._scrollingUp = false; + _this5._switchedDirection = false; + _this5._isAttached = false; + _this5._ticking = false; + _this5._fixedHeightContainer = false; + _this5._hasCalculatedSizes = false; + _this5._isAtTop = true; + + _initDefineProp(_this5, 'items', _descriptor, _this5); + + _initDefineProp(_this5, 'local', _descriptor2, _this5); + + _this5.element = element; + _this5.viewFactory = viewFactory; + _this5.instruction = instruction; + _this5.viewSlot = viewSlot; + _this5.observerLocator = observerLocator; + _this5.strategyLocator = strategyLocator; + _this5.viewStrategyLocator = viewStrategyLocator; + _this5.sourceExpression = (0, _repeatUtilities.getItemsSourceExpression)(_this5.instruction, 'virtual-repeat.for'); + _this5.isOneTime = (0, _repeatUtilities.isOneTime)(_this5.sourceExpression); + return _this5; + } + + VirtualRepeat.prototype.attached = function attached() { + var _this6 = this; + + this._isAttached = true; + var element = this.element; + this.viewStrategy = this.viewStrategyLocator.getStrategy(element); + this.scrollContainer = this.viewStrategy.getScrollContainer(element); + this.topBuffer = this.viewStrategy.createTopBufferElement(element); + this.bottomBuffer = this.viewStrategy.createBottomBufferElement(element); + this.itemsChanged(); + this.scrollListener = function () { + return _this6._onScroll(); + }; + var containerStyle = this.scrollContainer.style; + if (containerStyle.overflowY === 'scroll' || containerStyle.overflow === 'scroll' || containerStyle.overflowY === 'auto' || containerStyle.overflow === 'auto') { + this._fixedHeightContainer = true; + this.scrollContainer.addEventListener('scroll', this.scrollListener); + } else { + document.addEventListener('scroll', this.scrollListener); + } + }; + + VirtualRepeat.prototype.bind = function bind(bindingContext, overrideContext) { + this.scope = { bindingContext: bindingContext, overrideContext: overrideContext }; + this._itemsLength = this.items.length; + }; + + VirtualRepeat.prototype.call = function call(context, changes) { + this[context](this.items, changes); + }; + + VirtualRepeat.prototype.detached = function detached() { + this.scrollContainer.removeEventListener('scroll', this.scrollListener); + this._first = 0; + this._previousFirst = 0; + this._viewsLength = 0; + this._lastRebind = 0; + this._topBufferHeight = 0; + this._bottomBufferHeight = 0; + this._scrollingDown = false; + this._scrollingUp = false; + this._switchedDirection = false; + this._isAttached = false; + this._ticking = false; + this._hasCalculatedSizes = false; + this.viewStrategy.removeBufferElements(this.element, this.topBuffer, this.bottomBuffer); + this.isLastIndex = false; + this.scrollContainer = null; + this.scrollContainerHeight = null; + this.removeAllViews(true); + if (this.scrollHandler) { + this.scrollHandler.dispose(); + } + this._unsubscribeCollection(); + }; + + VirtualRepeat.prototype.itemsChanged = function itemsChanged() { + this._unsubscribeCollection(); + + if (!this.scope) { + return; + } + var items = this.items; + this.strategy = this.strategyLocator.getStrategy(items); + if (items.length > 0) { + this.strategy.createFirstItem(this); + } + this._calcInitialHeights(items.length); + if (!this.isOneTime && !this._observeInnerCollection()) { + this._observeCollection(); + } + + this.strategy.instanceChanged(this, items, this._viewsLength); + }; + + VirtualRepeat.prototype.unbind = function unbind() { + this.scope = null; + this.items = null; + this._itemsLength = null; + }; + + VirtualRepeat.prototype.handleCollectionMutated = function handleCollectionMutated(collection, changes) { + this._handlingMutations = true; + this._itemsLength = collection.length; + this.strategy.instanceMutated(this, collection, changes); + }; + + VirtualRepeat.prototype.handleInnerCollectionMutated = function handleInnerCollectionMutated(collection, changes) { + var _this7 = this; + + if (this.ignoreMutation) { + return; + } + this.ignoreMutation = true; + var newItems = this.sourceExpression.evaluate(this.scope, this.lookupFunctions); + this.observerLocator.taskQueue.queueMicroTask(function () { + return _this7.ignoreMutation = false; + }); + + if (newItems === this.items) { + this.itemsChanged(); + } else { + this.items = newItems; + } + }; + + VirtualRepeat.prototype._onScroll = function _onScroll() { + var _this8 = this; + + if (!this._ticking && !this._handlingMutations) { + requestAnimationFrame(function () { + return _this8._handleScroll(); + }); + this._ticking = true; + } + + if (this._handlingMutations) { + this._handlingMutations = false; + } + }; + + VirtualRepeat.prototype._handleScroll = function _handleScroll() { + if (!this._isAttached) { + return; + } + var itemHeight = this.itemHeight; + var scrollTop = this._fixedHeightContainer ? this.scrollContainer.scrollTop : pageYOffset - this.topBuffer.offsetTop; + this._first = Math.floor(scrollTop / itemHeight); + this._first = this._first < 0 ? 0 : this._first; + if (this._first > this.items.length - this.elementsInView) { + this._first = this.items.length - this.elementsInView; + } + this._checkScrolling(); + + if (this._scrollingDown) { + var viewsToMove = this._first - this._lastRebind; + if (this._switchedDirection) { + viewsToMove = this._isAtTop ? this._first : this._bufferSize - (this._lastRebind - this._first); + } + this._isAtTop = false; + this._lastRebind = this._first; + var movedViewsCount = this._moveViews(viewsToMove); + var adjustHeight = movedViewsCount < viewsToMove ? this._bottomBufferHeight : itemHeight * movedViewsCount; + this._switchedDirection = false; + this._topBufferHeight = this._topBufferHeight + adjustHeight; + this._bottomBufferHeight = this._bottomBufferHeight - adjustHeight; + if (this._bottomBufferHeight >= 0) { + this._adjustBufferHeights(); + } + } else if (this._scrollingUp) { + var _viewsToMove = this._lastRebind - this._first; + if (this._switchedDirection) { + if (this.isLastIndex) { + _viewsToMove = this.items.length - this._first - this.elementsInView; + } else { + _viewsToMove = this._bufferSize - (this._first - this._lastRebind); + } + } + this.isLastIndex = false; + this._lastRebind = this._first; + var _movedViewsCount = this._moveViews(_viewsToMove); + this.movedViewsCount = _movedViewsCount; + var _adjustHeight = _movedViewsCount < _viewsToMove ? this._topBufferHeight : itemHeight * _movedViewsCount; + this._switchedDirection = false; + this._topBufferHeight = this._topBufferHeight - _adjustHeight; + this._bottomBufferHeight = this._bottomBufferHeight + _adjustHeight; + if (this._topBufferHeight >= 0) { + this._adjustBufferHeights(); + } + } + this._previousFirst = this._first; + + this._ticking = false; + }; + + VirtualRepeat.prototype._checkScrolling = function _checkScrolling() { + if (this._first > this._previousFirst && (this._bottomBufferHeight > 0 || !this.isLastIndex)) { + if (!this._scrollingDown) { + this._scrollingDown = true; + this._scrollingUp = false; + this._switchedDirection = true; + } else { + this._switchedDirection = false; + } + this._isScrolling = true; + } else if (this._first < this._previousFirst && (this._topBufferHeight >= 0 || !this._isAtTop)) { + if (!this._scrollingUp) { + this._scrollingDown = false; + this._scrollingUp = true; + this._switchedDirection = true; + } else { + this._switchedDirection = false; + } + this._isScrolling = true; + } else { + this._isScrolling = false; + } + }; + + VirtualRepeat.prototype._adjustBufferHeights = function _adjustBufferHeights() { + this.topBuffer.setAttribute('style', 'height: ' + this._topBufferHeight + 'px'); + this.bottomBuffer.setAttribute('style', 'height: ' + this._bottomBufferHeight + 'px'); + }; + + VirtualRepeat.prototype._unsubscribeCollection = function _unsubscribeCollection() { + if (this.collectionObserver) { + this.collectionObserver.unsubscribe(this.callContext, this); + this.collectionObserver = null; + this.callContext = null; + } + }; + + VirtualRepeat.prototype._moveViews = function _moveViews(length) { + var _this9 = this; + + var getNextIndex = this._scrollingDown ? function (index, i) { + return index + i; + } : function (index, i) { + return index - i; + }; + var isAtFirstOrLastIndex = function isAtFirstOrLastIndex() { + return _this9._scrollingDown ? _this9.isLastIndex : _this9._isAtTop; + }; + var childrenLength = this.viewCount(); + var viewIndex = this._scrollingDown ? 0 : childrenLength - 1; + var items = this.items; + var index = this._scrollingDown ? this._getIndexOfLastView() + 1 : this._getIndexOfFirstView() - 1; + var i = 0; + while (i < length && !isAtFirstOrLastIndex()) { + var view = this.view(viewIndex); + var nextIndex = getNextIndex(index, i); + this.isLastIndex = nextIndex >= items.length - 1; + this._isAtTop = nextIndex <= 0; + if (!(isAtFirstOrLastIndex() && childrenLength >= items.length)) { + rebindAndMoveView(this, view, nextIndex, this._scrollingDown); + i++; + } + } + + return length - (length - i); + }; + + VirtualRepeat.prototype._getIndexOfLastView = function _getIndexOfLastView() { + return this.view(this.viewCount() - 1).overrideContext.$index; + }; + + VirtualRepeat.prototype._getIndexOfFirstView = function _getIndexOfFirstView() { + return this.view(0) ? this.view(0).overrideContext.$index : -1; + }; + + VirtualRepeat.prototype._calcInitialHeights = function _calcInitialHeights(itemsLength) { + if (this._viewsLength > 0 && this._itemsLength === itemsLength || itemsLength <= 0) { + return; + } + this._hasCalculatedSizes = true; + this._itemsLength = itemsLength; + var firstViewElement = this.view(0).firstChild.nextElementSibling; + this.itemHeight = calcOuterHeight(firstViewElement); + if (this.itemHeight <= 0) { + throw new Error('Could not calculate item height'); + } + this.scrollContainerHeight = this._fixedHeightContainer ? this._calcScrollHeight(this.scrollContainer) : document.documentElement.clientHeight; + this.elementsInView = Math.ceil(this.scrollContainerHeight / this.itemHeight) + 1; + this._viewsLength = this.elementsInView * 2 + this._bufferSize; + this._bottomBufferHeight = this.itemHeight * itemsLength - this.itemHeight * this._viewsLength; + if (this._bottomBufferHeight < 0) { + this._bottomBufferHeight = 0; + } + this.bottomBuffer.setAttribute('style', 'height: ' + this._bottomBufferHeight + 'px'); + this._topBufferHeight = 0; + this.topBuffer.setAttribute('style', 'height: ' + this._topBufferHeight + 'px'); + + this.scrollContainer.scrollTop = 0; + this._first = 0; + }; + + VirtualRepeat.prototype._calcScrollHeight = function _calcScrollHeight(element) { + var height = void 0; + height = element.getBoundingClientRect().height; + height -= getStyleValue(element, 'borderTopWidth'); + height -= getStyleValue(element, 'borderBottomWidth'); + return height; + }; + + VirtualRepeat.prototype._observeInnerCollection = function _observeInnerCollection() { + var items = this._getInnerCollection(); + var strategy = this.strategyLocator.getStrategy(items); + if (!strategy) { + return false; + } + this.collectionObserver = strategy.getCollectionObserver(this.observerLocator, items); + if (!this.collectionObserver) { + return false; + } + this.callContext = 'handleInnerCollectionMutated'; + this.collectionObserver.subscribe(this.callContext, this); + return true; + }; + + VirtualRepeat.prototype._getInnerCollection = function _getInnerCollection() { + var expression = (0, _repeatUtilities.unwrapExpression)(this.sourceExpression); + if (!expression) { + return null; + } + return expression.evaluate(this.scope, null); + }; + + VirtualRepeat.prototype._observeCollection = function _observeCollection() { + var items = this.items; + this.collectionObserver = this.strategy.getCollectionObserver(this.observerLocator, items); + if (this.collectionObserver) { + this.callContext = 'handleCollectionMutated'; + this.collectionObserver.subscribe(this.callContext, this); + } + }; + + VirtualRepeat.prototype.viewCount = function viewCount() { + return this.viewSlot.children.length; + }; + + VirtualRepeat.prototype.views = function views() { + return this.viewSlot.children; + }; + + VirtualRepeat.prototype.view = function view(index) { + return this.viewSlot.children[index]; + }; + + VirtualRepeat.prototype.addView = function addView(bindingContext, overrideContext) { + var view = this.viewFactory.create(); + view.bind(bindingContext, overrideContext); + this.viewSlot.add(view); + }; + + VirtualRepeat.prototype.insertView = function insertView(index, bindingContext, overrideContext) { + var view = this.viewFactory.create(); + view.bind(bindingContext, overrideContext); + this.viewSlot.insert(index, view); + }; + + VirtualRepeat.prototype.removeAllViews = function removeAllViews(returnToCache, skipAnimation) { + return this.viewSlot.removeAll(returnToCache, skipAnimation); + }; + + VirtualRepeat.prototype.removeView = function removeView(index, returnToCache, skipAnimation) { + return this.viewSlot.removeAt(index, returnToCache, skipAnimation); + }; + + VirtualRepeat.prototype.updateBindings = function updateBindings(view) { + var j = view.bindings.length; + while (j--) { + (0, _repeatUtilities.updateOneTimeBinding)(view.bindings[j]); + } + j = view.controllers.length; + while (j--) { + var k = view.controllers[j].boundProperties.length; + while (k--) { + var binding = view.controllers[j].boundProperties[k].binding; + (0, _repeatUtilities.updateOneTimeBinding)(binding); + } + } + }; + + return VirtualRepeat; +}(_aureliaTemplatingResources.AbstractRepeater), (_descriptor = _applyDecoratedDescriptor(_class2.prototype, 'items', [_aureliaTemplating.bindable], { + enumerable: true, + initializer: null +}), _descriptor2 = _applyDecoratedDescriptor(_class2.prototype, 'local', [_aureliaTemplating.bindable], { + enumerable: true, + initializer: null +})), _class2)) || _class) || _class) || _class); \ No newline at end of file diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index 7d5f939..f4fd0d0 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -1,3 +1,40 @@ +## 0.4.0 (2016-03-29) + + +#### Bug Fixes + +* **array-virtual-repeat-strategy:** + * add new items to the distance to bottom view port ([60383100](http://github.com/aurelia/ui-virtualization/commit/603831008f43fb1fb67a538b9191483c3a1259fc)) + * queue changes when animating ([c4fff68b](http://github.com/aurelia/ui-virtualization/commit/c4fff68b6403eca9a3802653f77d250ce749a1ab)) + * do not remove views when less than max rendered ([312f4caf](http://github.com/aurelia/ui-virtualization/commit/312f4caf2dac668225404e8d5dfa7602ffb87762)) + * handel added items when scrolled to bottom ([ed3b014c](http://github.com/aurelia/ui-virtualization/commit/ed3b014c63bcafc4df5f52ea72b4426c5f057260)) + * handle remove from bottom and top ([46c8ba71](http://github.com/aurelia/ui-virtualization/commit/46c8ba711c0455a5ba461fe2134f1f14c7af4c9d)) +* **utilities:** rename updateOverrideContexts to updateVirtualOverrideContexts ([c879a08f](http://github.com/aurelia/ui-virtualization/commit/c879a08f6f6b693fe0e4e0ba74302199c64cd6ef)) +* **view-strategy:** + * keep anchor node at bottom ([7857725f](http://github.com/aurelia/ui-virtualization/commit/7857725f9293b05a3a3672d6ca4902a0f050218f)) + * handle detached ([4f10fcc5](http://github.com/aurelia/ui-virtualization/commit/4f10fcc5ed3a62cd6ea2b9a72327117c111221cc)) +* **virtual-repeat:** + * initialize scroll at top to true ([7264d3d6](http://github.com/aurelia/ui-virtualization/commit/7264d3d6930293dc5bb869dd47ff464f070a333f)) + * stop updating when scrolling passed the list ([b7c19e6f](http://github.com/aurelia/ui-virtualization/commit/b7c19e6f111e4fa594d2f37d7b999039dd272a8b)) + * move views at top and bottom when virtualised ([7775796e](http://github.com/aurelia/ui-virtualization/commit/7775796eb2586bf27c3ed08ac8e5a420db97c6e0)) + * remove resize handling ([029e6efb](http://github.com/aurelia/ui-virtualization/commit/029e6efb21910c79a28ae6fe3c3968a39968da42)) + * support fixed height container ([80704074](http://github.com/aurelia/ui-virtualization/commit/807040741d900e503ed205c7f2cd971110b42aeb)) + * do not move view when at top or bottom ([dce3107a](http://github.com/aurelia/ui-virtualization/commit/dce3107a519c3d65a768b4c4221b8fa80f25b651)) + * handle bind with less items than whats fits in view port ([9e6f121a](http://github.com/aurelia/ui-virtualization/commit/9e6f121a26c5995ea4aa5ac262274e006db17683), closes [#39](http://github.com/aurelia/ui-virtualization/issues/39)) + * remove scrollList ([05ecd2ff](http://github.com/aurelia/ui-virtualization/commit/05ecd2ff8371c46e448af12ef9c3a5bb2cd73029)) + * handle remove items from list ([498296b1](http://github.com/aurelia/ui-virtualization/commit/498296b1b785cbbaa0476ec0c6e932daaa00643f)) + + +#### Features + +* **TableStrategy:** remove need for surrounding container ([617d7570](http://github.com/aurelia/ui-virtualization/commit/617d7570627ae4b3ff63049a09237446f0623d52)) +* **virtual-list:** remove VirtualList ([d52201c2](http://github.com/aurelia/ui-virtualization/commit/d52201c2cee28c25728fb1e00a9f6f90f9f8fc61)) +* **virtual-repeat:** + * support multiple virtual-repeat after each other ([5e99c07e](http://github.com/aurelia/ui-virtualization/commit/5e99c07e3a3a382f787d90eef27c0e39750e330a)) + * no need for surrounding container ([37c68bbd](http://github.com/aurelia/ui-virtualization/commit/37c68bbd2c5fc1b141aa97a16e40f94fcc95368e)) + * support inline virtualization ([4805482c](http://github.com/aurelia/ui-virtualization/commit/4805482ce46199abfef694a935e3f5e092dfcc80)) + + ### 0.3.2 (2016-03-01) diff --git a/package.json b/package.json index 373e590..e4fd108 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aurelia-ui-virtualization", - "version": "0.3.2", + "version": "0.4.0", "description": "A plugin that provides a virtualized repeater and other virtualization services.", "keywords": [ "aurelia",