diff --git a/README.md b/README.md index 45a6858..11deb15 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,28 @@ With a surrounding fixed height container with overflow scroll. Note that `overf If you are running the plugin in the `skeleton-naviagion` project, make sure to remove `overflow-x: hidden;` and `overflow-y: auto;` from `.page-host` in `styles.css`. +#### infinite scroll +```html + +``` + +```javascript +export class MyVirtualList { + items = ['Foo', 'Bar', 'Baz']; + getMore() { + for(let i = 0; i < 100; ++i) { + this.items.push('item' + i); + } + } +} +``` +The `virtual-repeat-next` attribute can accept a function, a promise, or a function that returns a promise. +The bound function will be called when the scroll container has reached a point where there are no more items to move into the DOM (i.e. when it reaches the end of a list). + ## [Demo](http://aurelia.io/ui-virtualization/) ## Platform Support diff --git a/build/tasks/test.js b/build/tasks/test.js index c7af441..d26e93c 100644 --- a/build/tasks/test.js +++ b/build/tasks/test.js @@ -11,6 +11,16 @@ gulp.task('test', function (done) { }, done).start(); }); +/** + * Run test and watch for changes + */ +gulp.task('test:watch', function (done) { + new Karma({ + configFile: __dirname + '/../../karma.conf.js', + singleRun: false + }, done).start(); +}); + /** * Watch for file changes and re-run tests on each change */ diff --git a/config.js b/config.js index b175fed..381ef54 100644 --- a/config.js +++ b/config.js @@ -14,10 +14,10 @@ System.config({ }, map: { - "aurelia-binding": "npm:aurelia-binding@1.0.0-rc.1.0.0", - "aurelia-bootstrapper": "npm:aurelia-bootstrapper@1.0.0-beta.2.0.1", + "aurelia-binding": "npm:aurelia-binding@1.0.0-rc.1.0.2", + "aurelia-bootstrapper": "npm:aurelia-bootstrapper@1.0.0-rc.1.0.1", "aurelia-dependency-injection": "npm:aurelia-dependency-injection@1.0.0-rc.1.0.0", - "aurelia-framework": "npm:aurelia-framework@1.0.0-rc.1.0.0", + "aurelia-framework": "npm:aurelia-framework@1.0.0-rc.1.0.1", "aurelia-logging": "npm:aurelia-logging@1.0.0-rc.1.0.0", "aurelia-logging-console": "npm:aurelia-logging-console@1.0.0-rc.1.0.0", "aurelia-pal": "npm:aurelia-pal@1.0.0-rc.1.0.0", @@ -56,15 +56,15 @@ System.config({ "process": "github:jspm/nodelibs-process@0.1.2", "util": "npm:util@0.10.3" }, - "npm:aurelia-binding@1.0.0-rc.1.0.0": { + "npm:aurelia-binding@1.0.0-rc.1.0.2": { "aurelia-logging": "npm:aurelia-logging@1.0.0-rc.1.0.0", "aurelia-metadata": "npm:aurelia-metadata@1.0.0-rc.1.0.0", "aurelia-pal": "npm:aurelia-pal@1.0.0-rc.1.0.0", "aurelia-task-queue": "npm:aurelia-task-queue@1.0.0-rc.1.0.0" }, - "npm:aurelia-bootstrapper@1.0.0-beta.2.0.1": { + "npm:aurelia-bootstrapper@1.0.0-rc.1.0.1": { "aurelia-event-aggregator": "npm:aurelia-event-aggregator@1.0.0-rc.1.0.0", - "aurelia-framework": "npm:aurelia-framework@1.0.0-rc.1.0.0", + "aurelia-framework": "npm:aurelia-framework@1.0.0-rc.1.0.1", "aurelia-history": "npm:aurelia-history@1.0.0-rc.1.0.0", "aurelia-history-browser": "npm:aurelia-history-browser@1.0.0-rc.1.0.0", "aurelia-loader-default": "npm:aurelia-loader-default@1.0.0-rc.1.0.0", @@ -85,8 +85,8 @@ System.config({ "npm:aurelia-event-aggregator@1.0.0-rc.1.0.0": { "aurelia-logging": "npm:aurelia-logging@1.0.0-rc.1.0.0" }, - "npm:aurelia-framework@1.0.0-rc.1.0.0": { - "aurelia-binding": "npm:aurelia-binding@1.0.0-rc.1.0.0", + "npm:aurelia-framework@1.0.0-rc.1.0.1": { + "aurelia-binding": "npm:aurelia-binding@1.0.0-rc.1.0.2", "aurelia-dependency-injection": "npm:aurelia-dependency-injection@1.0.0-rc.1.0.0", "aurelia-loader": "npm:aurelia-loader@1.0.0-rc.1.0.0", "aurelia-logging": "npm:aurelia-logging@1.0.0-rc.1.0.0", @@ -136,12 +136,12 @@ System.config({ "aurelia-pal": "npm:aurelia-pal@1.0.0-rc.1.0.0" }, "npm:aurelia-templating-binding@1.0.0-rc.1.0.0": { - "aurelia-binding": "npm:aurelia-binding@1.0.0-rc.1.0.0", + "aurelia-binding": "npm:aurelia-binding@1.0.0-rc.1.0.2", "aurelia-logging": "npm:aurelia-logging@1.0.0-rc.1.0.0", "aurelia-templating": "npm:aurelia-templating@1.0.0-rc.1.0.0" }, "npm:aurelia-templating-resources@1.0.0-rc.1.0.0": { - "aurelia-binding": "npm:aurelia-binding@1.0.0-rc.1.0.0", + "aurelia-binding": "npm:aurelia-binding@1.0.0-rc.1.0.2", "aurelia-dependency-injection": "npm:aurelia-dependency-injection@1.0.0-rc.1.0.0", "aurelia-loader": "npm:aurelia-loader@1.0.0-rc.1.0.0", "aurelia-logging": "npm:aurelia-logging@1.0.0-rc.1.0.0", @@ -161,7 +161,7 @@ System.config({ "aurelia-templating": "npm:aurelia-templating@1.0.0-rc.1.0.0" }, "npm:aurelia-templating@1.0.0-rc.1.0.0": { - "aurelia-binding": "npm:aurelia-binding@1.0.0-rc.1.0.0", + "aurelia-binding": "npm:aurelia-binding@1.0.0-rc.1.0.2", "aurelia-dependency-injection": "npm:aurelia-dependency-injection@1.0.0-rc.1.0.0", "aurelia-loader": "npm:aurelia-loader@1.0.0-rc.1.0.0", "aurelia-logging": "npm:aurelia-logging@1.0.0-rc.1.0.0", diff --git a/dist/amd/aurelia-ui-virtualization.js b/dist/amd/aurelia-ui-virtualization.js index fd730f6..5593c9a 100644 --- a/dist/amd/aurelia-ui-virtualization.js +++ b/dist/amd/aurelia-ui-virtualization.js @@ -1,14 +1,15 @@ -define(['exports', './virtual-repeat'], function (exports, _virtualRepeat) { +define(['exports', './virtual-repeat', './virtual-repeat-next'], function (exports, _virtualRepeat, _virtualRepeatNext) { 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); - exports.VirtualRepeat = undefined; + exports.VirtualRepeatNext = exports.VirtualRepeat = undefined; exports.configure = configure; function configure(config) { - config.globalResources('./virtual-repeat'); + config.globalResources('./virtual-repeat', './virtual-repeat-next'); } exports.VirtualRepeat = _virtualRepeat.VirtualRepeat; + exports.VirtualRepeatNext = _virtualRepeatNext.VirtualRepeatNext; }); \ No newline at end of file diff --git a/dist/amd/template-strategy.js b/dist/amd/template-strategy.js index 162d844..abc0a17 100644 --- a/dist/amd/template-strategy.js +++ b/dist/amd/template-strategy.js @@ -46,11 +46,13 @@ define(['exports', 'aurelia-pal', 'aurelia-templating', './utilities'], function }; TableStrategy.prototype.moveViewFirst = function moveViewFirst(view, topBuffer) { - (0, _utilities.insertBeforeNode)(view, _aureliaPal.DOM.nextElementSibling(topBuffer.parentNode).previousSibling); + (0, _utilities.insertBeforeNode)(view, _aureliaPal.DOM.nextElementSibling(topBuffer.parentNode)); }; TableStrategy.prototype.moveViewLast = function moveViewLast(view, bottomBuffer) { - (0, _utilities.insertBeforeNode)(view, bottomBuffer.parentNode); + var previousSibling = bottomBuffer.parentNode.previousSibling; + var referenceNode = previousSibling.nodeType === 8 && previousSibling.data === 'anchor' ? previousSibling : bottomBuffer.parentNode; + (0, _utilities.insertBeforeNode)(view, referenceNode); }; TableStrategy.prototype.createTopBufferElement = function createTopBufferElement(element) { diff --git a/dist/amd/virtual-repeat-next.js b/dist/amd/virtual-repeat-next.js new file mode 100644 index 0000000..ffc164e --- /dev/null +++ b/dist/amd/virtual-repeat-next.js @@ -0,0 +1,26 @@ +define(['exports', 'aurelia-templating'], function (exports, _aureliaTemplating) { + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.VirtualRepeatNext = undefined; + + + + var _dec, _class; + + var VirtualRepeatNext = exports.VirtualRepeatNext = (_dec = (0, _aureliaTemplating.customAttribute)('virtual-repeat-next'), _dec(_class = function () { + function VirtualRepeatNext() { + + } + + VirtualRepeatNext.prototype.attached = function attached() {}; + + VirtualRepeatNext.prototype.bind = function bind(bindingContext, overrideContext) { + this.scope = { bindingContext: bindingContext, overrideContext: overrideContext }; + }; + + return VirtualRepeatNext; + }()) || _class); +}); \ No newline at end of file diff --git a/dist/amd/virtual-repeat.js b/dist/amd/virtual-repeat.js index 599fd2a..be8ff60 100644 --- a/dist/amd/virtual-repeat.js +++ b/dist/amd/virtual-repeat.js @@ -6,6 +6,12 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-t }); exports.VirtualRepeat = undefined; + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { + return typeof obj; + } : function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; + }; + function _initDefineProp(target, property, descriptor, context) { if (!descriptor) return; Object.defineProperty(target, property, { @@ -103,6 +109,7 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-t _this._fixedHeightContainer = false; _this._hasCalculatedSizes = false; _this._isAtTop = true; + _this._calledGetMore = false; _initDefineProp(_this, 'items', _descriptor, _this); @@ -276,6 +283,7 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-t this._lastRebind = this._first; var movedViewsCount = this._moveViews(viewsToMove); var adjustHeight = movedViewsCount < viewsToMove ? this._bottomBufferHeight : itemHeight * movedViewsCount; + this._getMore(); this._switchedDirection = false; this._topBufferHeight = this._topBufferHeight + adjustHeight; this._bottomBufferHeight = this._bottomBufferHeight - adjustHeight; @@ -308,6 +316,45 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-t this._ticking = false; }; + VirtualRepeat.prototype._getMore = function _getMore() { + var _this5 = this; + + if (this.isLastIndex) { + if (!this._calledGetMore) { + var _ret = function () { + var getMoreFunc = _this5.view(0).firstChild.getAttribute('virtual-repeat-next'); + if (!getMoreFunc) { + return { + v: void 0 + }; + } + var getMore = _this5.scope.overrideContext.bindingContext[getMoreFunc]; + + _this5.observerLocator.taskQueue.queueMicroTask(function () { + _this5._calledGetMore = true; + if (getMore instanceof Promise) { + return getMore.then(function () { + _this5._calledGetMore = false; + }); + } else if (typeof getMore === 'function') { + var result = getMore.bind(_this5.scope.overrideContext.bindingContext)(); + if (result instanceof Promise) { + return result.then(function () { + _this5._calledGetMore = false; + }); + } else { + _this5._calledGetMore = false; + return; + } + } + }); + }(); + + if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; + } + } + }; + VirtualRepeat.prototype._checkScrolling = function _checkScrolling() { if (this._first > this._previousFirst && (this._bottomBufferHeight > 0 || !this.isLastIndex)) { if (!this._scrollingDown) { @@ -346,7 +393,7 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-t }; VirtualRepeat.prototype._moveViews = function _moveViews(length) { - var _this5 = this; + var _this6 = this; var getNextIndex = this._scrollingDown ? function (index, i) { return index + i; @@ -354,7 +401,7 @@ define(['exports', 'aurelia-dependency-injection', 'aurelia-binding', 'aurelia-t return index - i; }; var isAtFirstOrLastIndex = function isAtFirstOrLastIndex() { - return _this5._scrollingDown ? _this5.isLastIndex : _this5._isAtTop; + return _this6._scrollingDown ? _this6.isLastIndex : _this6._isAtTop; }; var childrenLength = this.viewCount(); var viewIndex = this._scrollingDown ? 0 : childrenLength - 1; diff --git a/dist/aurelia-ui-virtualization.d.ts b/dist/aurelia-ui-virtualization.d.ts index 7c3d897..20cd987 100644 --- a/dist/aurelia-ui-virtualization.d.ts +++ b/dist/aurelia-ui-virtualization.d.ts @@ -1,3 +1,13 @@ +import { + customAttribute, + View, + BoundViewFactory, + ViewSlot, + ViewResources, + TargetInstruction, + bindable, + templateController +} from 'aurelia-templating'; import { updateOverrideContext, ArrayRepeatStrategy, @@ -10,16 +20,6 @@ import { updateOneTimeBinding, viewsRequireLifecycle } from 'aurelia-templating-resources'; -import { - View, - BoundViewFactory, - ViewSlot, - ViewResources, - TargetInstruction, - customAttribute, - bindable, - templateController -} from 'aurelia-templating'; import { DOM } from 'aurelia-pal'; @@ -43,6 +43,13 @@ export declare class DomHelper { getElementDistanceToTopOfDocument(element: Element): number; hasOverflowScroll(element: Element): boolean; } + +//Placeholder attribute to prohibit use of this attribute name in other places +export declare class VirtualRepeatNext { + constructor(); + attached(): any; + bind(bindingContext?: any, overrideContext?: any): void; +} export declare function calcOuterHeight(element: Element): number; export declare function insertBeforeNode(view: View, bottomBuffer: number): void; diff --git a/dist/aurelia-ui-virtualization.js b/dist/aurelia-ui-virtualization.js index 9610262..c591270 100644 --- a/dist/aurelia-ui-virtualization.js +++ b/dist/aurelia-ui-virtualization.js @@ -1,5 +1,5 @@ +import {customAttribute,View,BoundViewFactory,ViewSlot,ViewResources,TargetInstruction,bindable,templateController} from 'aurelia-templating'; import {updateOverrideContext,ArrayRepeatStrategy,createFullOverrideContext,RepeatStrategyLocator,AbstractRepeater,getItemsSourceExpression,isOneTime,unwrapExpression,updateOneTimeBinding,viewsRequireLifecycle} from 'aurelia-templating-resources'; -import {View,BoundViewFactory,ViewSlot,ViewResources,TargetInstruction,customAttribute,bindable,templateController} from 'aurelia-templating'; import {DOM} from 'aurelia-pal'; import {inject} from 'aurelia-dependency-injection'; import {ObserverLocator} from 'aurelia-binding'; @@ -20,6 +20,23 @@ export class DomHelper { } } +//Placeholder attribute to prohibit use of this attribute name in other places + +@customAttribute('virtual-repeat-next') +export class VirtualRepeatNext { + + constructor(){ + } + + attached(){ + } + + bind(bindingContext, overrideContext): void { + this.scope = { bindingContext, overrideContext }; + } + +} + export function calcOuterHeight(element: Element): number { let height; height = element.getBoundingClientRect().height; @@ -393,11 +410,13 @@ export class TableStrategy { } moveViewFirst(view: View, topBuffer: Element): void { - insertBeforeNode(view, DOM.nextElementSibling(topBuffer.parentNode).previousSibling); + insertBeforeNode(view, DOM.nextElementSibling(topBuffer.parentNode)); } moveViewLast(view: View, bottomBuffer: Element): void { - insertBeforeNode(view, bottomBuffer.parentNode); + let previousSibling = bottomBuffer.parentNode.previousSibling; + let referenceNode = previousSibling.nodeType === 8 && previousSibling.data === 'anchor' ? previousSibling : bottomBuffer.parentNode; + insertBeforeNode(view, referenceNode); } createTopBufferElement(element: Element): Element { @@ -507,6 +526,7 @@ export class VirtualRepeat extends AbstractRepeater { _fixedHeightContainer = false; _hasCalculatedSizes = false; _isAtTop = true; + _calledGetMore = false; @bindable items @bindable local @@ -686,6 +706,7 @@ export class VirtualRepeat extends AbstractRepeater { this._lastRebind = this._first; let movedViewsCount = this._moveViews(viewsToMove); let adjustHeight = movedViewsCount < viewsToMove ? this._bottomBufferHeight : itemHeight * movedViewsCount; + this._getMore(); this._switchedDirection = false; this._topBufferHeight = this._topBufferHeight + adjustHeight; this._bottomBufferHeight = this._bottomBufferHeight - adjustHeight; @@ -718,6 +739,38 @@ export class VirtualRepeat extends AbstractRepeater { this._ticking = false; } + _getMore(): void{ + if(this.isLastIndex){ + if(!this._calledGetMore){ + let getMoreFunc = this.view(0).firstChild.getAttribute('virtual-repeat-next'); + if(!getMoreFunc){ + //break down the boogie + return; + } + let getMore = this.scope.overrideContext.bindingContext[getMoreFunc]; + + this.observerLocator.taskQueue.queueMicroTask(() =>{ + this._calledGetMore = true; + if(getMore instanceof Promise){ + return getMore.then(() => { + this._calledGetMore = false; //Reset for the next time + }) + } else if (typeof getMore === 'function'){ + let result = getMore.bind(this.scope.overrideContext.bindingContext)(); + if(result instanceof Promise){ + return result.then(() => { + this._calledGetMore = false; //Reset for the next time + }) + } else { + this._calledGetMore = false; //Reset for the next time + return; + } + } + }); + } + } + } + _checkScrolling(): void { if (this._first > this._previousFirst && (this._bottomBufferHeight > 0 || !this.isLastIndex)) { if (!this._scrollingDown) { diff --git a/dist/commonjs/aurelia-ui-virtualization.js b/dist/commonjs/aurelia-ui-virtualization.js index fae8111..2613d33 100644 --- a/dist/commonjs/aurelia-ui-virtualization.js +++ b/dist/commonjs/aurelia-ui-virtualization.js @@ -3,13 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true }); -exports.VirtualRepeat = undefined; +exports.VirtualRepeatNext = exports.VirtualRepeat = undefined; exports.configure = configure; var _virtualRepeat = require('./virtual-repeat'); +var _virtualRepeatNext = require('./virtual-repeat-next'); + function configure(config) { - config.globalResources('./virtual-repeat'); + config.globalResources('./virtual-repeat', './virtual-repeat-next'); } -exports.VirtualRepeat = _virtualRepeat.VirtualRepeat; \ No newline at end of file +exports.VirtualRepeat = _virtualRepeat.VirtualRepeat; +exports.VirtualRepeatNext = _virtualRepeatNext.VirtualRepeatNext; \ No newline at end of file diff --git a/dist/commonjs/template-strategy.js b/dist/commonjs/template-strategy.js index 4df5160..b6c08d6 100644 --- a/dist/commonjs/template-strategy.js +++ b/dist/commonjs/template-strategy.js @@ -51,11 +51,13 @@ var TableStrategy = exports.TableStrategy = function () { }; TableStrategy.prototype.moveViewFirst = function moveViewFirst(view, topBuffer) { - (0, _utilities.insertBeforeNode)(view, _aureliaPal.DOM.nextElementSibling(topBuffer.parentNode).previousSibling); + (0, _utilities.insertBeforeNode)(view, _aureliaPal.DOM.nextElementSibling(topBuffer.parentNode)); }; TableStrategy.prototype.moveViewLast = function moveViewLast(view, bottomBuffer) { - (0, _utilities.insertBeforeNode)(view, bottomBuffer.parentNode); + var previousSibling = bottomBuffer.parentNode.previousSibling; + var referenceNode = previousSibling.nodeType === 8 && previousSibling.data === 'anchor' ? previousSibling : bottomBuffer.parentNode; + (0, _utilities.insertBeforeNode)(view, referenceNode); }; TableStrategy.prototype.createTopBufferElement = function createTopBufferElement(element) { diff --git a/dist/commonjs/virtual-repeat-next.js b/dist/commonjs/virtual-repeat-next.js new file mode 100644 index 0000000..700dd2c --- /dev/null +++ b/dist/commonjs/virtual-repeat-next.js @@ -0,0 +1,26 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.VirtualRepeatNext = undefined; + +var _dec, _class; + +var _aureliaTemplating = require('aurelia-templating'); + + + +var VirtualRepeatNext = exports.VirtualRepeatNext = (_dec = (0, _aureliaTemplating.customAttribute)('virtual-repeat-next'), _dec(_class = function () { + function VirtualRepeatNext() { + + } + + VirtualRepeatNext.prototype.attached = function attached() {}; + + VirtualRepeatNext.prototype.bind = function bind(bindingContext, overrideContext) { + this.scope = { bindingContext: bindingContext, overrideContext: overrideContext }; + }; + + return VirtualRepeatNext; +}()) || _class); \ No newline at end of file diff --git a/dist/commonjs/virtual-repeat.js b/dist/commonjs/virtual-repeat.js index 5791dd1..0128344 100644 --- a/dist/commonjs/virtual-repeat.js +++ b/dist/commonjs/virtual-repeat.js @@ -5,6 +5,8 @@ Object.defineProperty(exports, "__esModule", { }); exports.VirtualRepeat = undefined; +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + var _dec, _dec2, _class, _desc, _value, _class2, _descriptor, _descriptor2; var _aureliaDependencyInjection = require('aurelia-dependency-injection'); @@ -100,6 +102,7 @@ var VirtualRepeat = exports.VirtualRepeat = (_dec = (0, _aureliaTemplating.custo _this._fixedHeightContainer = false; _this._hasCalculatedSizes = false; _this._isAtTop = true; + _this._calledGetMore = false; _initDefineProp(_this, 'items', _descriptor, _this); @@ -273,6 +276,7 @@ var VirtualRepeat = exports.VirtualRepeat = (_dec = (0, _aureliaTemplating.custo this._lastRebind = this._first; var movedViewsCount = this._moveViews(viewsToMove); var adjustHeight = movedViewsCount < viewsToMove ? this._bottomBufferHeight : itemHeight * movedViewsCount; + this._getMore(); this._switchedDirection = false; this._topBufferHeight = this._topBufferHeight + adjustHeight; this._bottomBufferHeight = this._bottomBufferHeight - adjustHeight; @@ -305,6 +309,45 @@ var VirtualRepeat = exports.VirtualRepeat = (_dec = (0, _aureliaTemplating.custo this._ticking = false; }; + VirtualRepeat.prototype._getMore = function _getMore() { + var _this5 = this; + + if (this.isLastIndex) { + if (!this._calledGetMore) { + var _ret = function () { + var getMoreFunc = _this5.view(0).firstChild.getAttribute('virtual-repeat-next'); + if (!getMoreFunc) { + return { + v: void 0 + }; + } + var getMore = _this5.scope.overrideContext.bindingContext[getMoreFunc]; + + _this5.observerLocator.taskQueue.queueMicroTask(function () { + _this5._calledGetMore = true; + if (getMore instanceof Promise) { + return getMore.then(function () { + _this5._calledGetMore = false; + }); + } else if (typeof getMore === 'function') { + var result = getMore.bind(_this5.scope.overrideContext.bindingContext)(); + if (result instanceof Promise) { + return result.then(function () { + _this5._calledGetMore = false; + }); + } else { + _this5._calledGetMore = false; + return; + } + } + }); + }(); + + if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; + } + } + }; + VirtualRepeat.prototype._checkScrolling = function _checkScrolling() { if (this._first > this._previousFirst && (this._bottomBufferHeight > 0 || !this.isLastIndex)) { if (!this._scrollingDown) { @@ -343,7 +386,7 @@ var VirtualRepeat = exports.VirtualRepeat = (_dec = (0, _aureliaTemplating.custo }; VirtualRepeat.prototype._moveViews = function _moveViews(length) { - var _this5 = this; + var _this6 = this; var getNextIndex = this._scrollingDown ? function (index, i) { return index + i; @@ -351,7 +394,7 @@ var VirtualRepeat = exports.VirtualRepeat = (_dec = (0, _aureliaTemplating.custo return index - i; }; var isAtFirstOrLastIndex = function isAtFirstOrLastIndex() { - return _this5._scrollingDown ? _this5.isLastIndex : _this5._isAtTop; + return _this6._scrollingDown ? _this6.isLastIndex : _this6._isAtTop; }; var childrenLength = this.viewCount(); var viewIndex = this._scrollingDown ? 0 : childrenLength - 1; diff --git a/dist/es2015/aurelia-ui-virtualization.js b/dist/es2015/aurelia-ui-virtualization.js index 91b4a83..3ef0089 100644 --- a/dist/es2015/aurelia-ui-virtualization.js +++ b/dist/es2015/aurelia-ui-virtualization.js @@ -1,7 +1,8 @@ import { VirtualRepeat } from './virtual-repeat'; +import { VirtualRepeatNext } from './virtual-repeat-next'; export function configure(config) { - config.globalResources('./virtual-repeat'); + config.globalResources('./virtual-repeat', './virtual-repeat-next'); } -export { VirtualRepeat }; \ No newline at end of file +export { VirtualRepeat, VirtualRepeatNext }; \ No newline at end of file diff --git a/dist/es2015/template-strategy.js b/dist/es2015/template-strategy.js index 124aa00..705c23d 100644 --- a/dist/es2015/template-strategy.js +++ b/dist/es2015/template-strategy.js @@ -32,11 +32,13 @@ export let TableStrategy = class TableStrategy { } moveViewFirst(view, topBuffer) { - insertBeforeNode(view, DOM.nextElementSibling(topBuffer.parentNode).previousSibling); + insertBeforeNode(view, DOM.nextElementSibling(topBuffer.parentNode)); } moveViewLast(view, bottomBuffer) { - insertBeforeNode(view, bottomBuffer.parentNode); + let previousSibling = bottomBuffer.parentNode.previousSibling; + let referenceNode = previousSibling.nodeType === 8 && previousSibling.data === 'anchor' ? previousSibling : bottomBuffer.parentNode; + insertBeforeNode(view, referenceNode); } createTopBufferElement(element) { diff --git a/dist/es2015/virtual-repeat-next.js b/dist/es2015/virtual-repeat-next.js new file mode 100644 index 0000000..d946dc6 --- /dev/null +++ b/dist/es2015/virtual-repeat-next.js @@ -0,0 +1,15 @@ +var _dec, _class; + +import { customAttribute } from 'aurelia-templating'; + +export let VirtualRepeatNext = (_dec = customAttribute('virtual-repeat-next'), _dec(_class = class VirtualRepeatNext { + + constructor() {} + + attached() {} + + bind(bindingContext, overrideContext) { + this.scope = { bindingContext, overrideContext }; + } + +}) || _class); \ No newline at end of file diff --git a/dist/es2015/virtual-repeat.js b/dist/es2015/virtual-repeat.js index c2bd0f7..3de1508 100644 --- a/dist/es2015/virtual-repeat.js +++ b/dist/es2015/virtual-repeat.js @@ -75,6 +75,7 @@ export let VirtualRepeat = (_dec = customAttribute('virtual-repeat'), _dec2 = in this._fixedHeightContainer = false; this._hasCalculatedSizes = false; this._isAtTop = true; + this._calledGetMore = false; _initDefineProp(this, 'items', _descriptor, this); @@ -235,6 +236,7 @@ export let VirtualRepeat = (_dec = customAttribute('virtual-repeat'), _dec2 = in this._lastRebind = this._first; let movedViewsCount = this._moveViews(viewsToMove); let adjustHeight = movedViewsCount < viewsToMove ? this._bottomBufferHeight : itemHeight * movedViewsCount; + this._getMore(); this._switchedDirection = false; this._topBufferHeight = this._topBufferHeight + adjustHeight; this._bottomBufferHeight = this._bottomBufferHeight - adjustHeight; @@ -267,6 +269,37 @@ export let VirtualRepeat = (_dec = customAttribute('virtual-repeat'), _dec2 = in this._ticking = false; } + _getMore() { + if (this.isLastIndex) { + if (!this._calledGetMore) { + let getMoreFunc = this.view(0).firstChild.getAttribute('virtual-repeat-next'); + if (!getMoreFunc) { + return; + } + let getMore = this.scope.overrideContext.bindingContext[getMoreFunc]; + + this.observerLocator.taskQueue.queueMicroTask(() => { + this._calledGetMore = true; + if (getMore instanceof Promise) { + return getMore.then(() => { + this._calledGetMore = false; + }); + } else if (typeof getMore === 'function') { + let result = getMore.bind(this.scope.overrideContext.bindingContext)(); + if (result instanceof Promise) { + return result.then(() => { + this._calledGetMore = false; + }); + } else { + this._calledGetMore = false; + return; + } + } + }); + } + } + } + _checkScrolling() { if (this._first > this._previousFirst && (this._bottomBufferHeight > 0 || !this.isLastIndex)) { if (!this._scrollingDown) { diff --git a/dist/native-modules/aurelia-ui-virtualization.js b/dist/native-modules/aurelia-ui-virtualization.js index 91b4a83..3ef0089 100644 --- a/dist/native-modules/aurelia-ui-virtualization.js +++ b/dist/native-modules/aurelia-ui-virtualization.js @@ -1,7 +1,8 @@ import { VirtualRepeat } from './virtual-repeat'; +import { VirtualRepeatNext } from './virtual-repeat-next'; export function configure(config) { - config.globalResources('./virtual-repeat'); + config.globalResources('./virtual-repeat', './virtual-repeat-next'); } -export { VirtualRepeat }; \ No newline at end of file +export { VirtualRepeat, VirtualRepeatNext }; \ No newline at end of file diff --git a/dist/native-modules/template-strategy.js b/dist/native-modules/template-strategy.js index bfab251..2b7e9a6 100644 --- a/dist/native-modules/template-strategy.js +++ b/dist/native-modules/template-strategy.js @@ -42,11 +42,13 @@ export var TableStrategy = function () { }; TableStrategy.prototype.moveViewFirst = function moveViewFirst(view, topBuffer) { - insertBeforeNode(view, DOM.nextElementSibling(topBuffer.parentNode).previousSibling); + insertBeforeNode(view, DOM.nextElementSibling(topBuffer.parentNode)); }; TableStrategy.prototype.moveViewLast = function moveViewLast(view, bottomBuffer) { - insertBeforeNode(view, bottomBuffer.parentNode); + var previousSibling = bottomBuffer.parentNode.previousSibling; + var referenceNode = previousSibling.nodeType === 8 && previousSibling.data === 'anchor' ? previousSibling : bottomBuffer.parentNode; + insertBeforeNode(view, referenceNode); }; TableStrategy.prototype.createTopBufferElement = function createTopBufferElement(element) { diff --git a/dist/native-modules/virtual-repeat-next.js b/dist/native-modules/virtual-repeat-next.js new file mode 100644 index 0000000..ef0cbc8 --- /dev/null +++ b/dist/native-modules/virtual-repeat-next.js @@ -0,0 +1,19 @@ +var _dec, _class; + + + +import { customAttribute } from 'aurelia-templating'; + +export var VirtualRepeatNext = (_dec = customAttribute('virtual-repeat-next'), _dec(_class = function () { + function VirtualRepeatNext() { + + } + + VirtualRepeatNext.prototype.attached = function attached() {}; + + VirtualRepeatNext.prototype.bind = function bind(bindingContext, overrideContext) { + this.scope = { bindingContext: bindingContext, overrideContext: overrideContext }; + }; + + return VirtualRepeatNext; +}()) || _class); \ No newline at end of file diff --git a/dist/native-modules/virtual-repeat.js b/dist/native-modules/virtual-repeat.js index eb1ab6b..3eb9013 100644 --- a/dist/native-modules/virtual-repeat.js +++ b/dist/native-modules/virtual-repeat.js @@ -1,3 +1,5 @@ +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + var _dec, _dec2, _class, _desc, _value, _class2, _descriptor, _descriptor2; function _initDefineProp(target, property, descriptor, context) { @@ -85,6 +87,7 @@ export var VirtualRepeat = (_dec = customAttribute('virtual-repeat'), _dec2 = in _this._fixedHeightContainer = false; _this._hasCalculatedSizes = false; _this._isAtTop = true; + _this._calledGetMore = false; _initDefineProp(_this, 'items', _descriptor, _this); @@ -258,6 +261,7 @@ export var VirtualRepeat = (_dec = customAttribute('virtual-repeat'), _dec2 = in this._lastRebind = this._first; var movedViewsCount = this._moveViews(viewsToMove); var adjustHeight = movedViewsCount < viewsToMove ? this._bottomBufferHeight : itemHeight * movedViewsCount; + this._getMore(); this._switchedDirection = false; this._topBufferHeight = this._topBufferHeight + adjustHeight; this._bottomBufferHeight = this._bottomBufferHeight - adjustHeight; @@ -290,6 +294,45 @@ export var VirtualRepeat = (_dec = customAttribute('virtual-repeat'), _dec2 = in this._ticking = false; }; + VirtualRepeat.prototype._getMore = function _getMore() { + var _this5 = this; + + if (this.isLastIndex) { + if (!this._calledGetMore) { + var _ret = function () { + var getMoreFunc = _this5.view(0).firstChild.getAttribute('virtual-repeat-next'); + if (!getMoreFunc) { + return { + v: void 0 + }; + } + var getMore = _this5.scope.overrideContext.bindingContext[getMoreFunc]; + + _this5.observerLocator.taskQueue.queueMicroTask(function () { + _this5._calledGetMore = true; + if (getMore instanceof Promise) { + return getMore.then(function () { + _this5._calledGetMore = false; + }); + } else if (typeof getMore === 'function') { + var result = getMore.bind(_this5.scope.overrideContext.bindingContext)(); + if (result instanceof Promise) { + return result.then(function () { + _this5._calledGetMore = false; + }); + } else { + _this5._calledGetMore = false; + return; + } + } + }); + }(); + + if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; + } + } + }; + VirtualRepeat.prototype._checkScrolling = function _checkScrolling() { if (this._first > this._previousFirst && (this._bottomBufferHeight > 0 || !this.isLastIndex)) { if (!this._scrollingDown) { @@ -328,7 +371,7 @@ export var VirtualRepeat = (_dec = customAttribute('virtual-repeat'), _dec2 = in }; VirtualRepeat.prototype._moveViews = function _moveViews(length) { - var _this5 = this; + var _this6 = this; var getNextIndex = this._scrollingDown ? function (index, i) { return index + i; @@ -336,7 +379,7 @@ export var VirtualRepeat = (_dec = customAttribute('virtual-repeat'), _dec2 = in return index - i; }; var isAtFirstOrLastIndex = function isAtFirstOrLastIndex() { - return _this5._scrollingDown ? _this5.isLastIndex : _this5._isAtTop; + return _this6._scrollingDown ? _this6.isLastIndex : _this6._isAtTop; }; var childrenLength = this.viewCount(); var viewIndex = this._scrollingDown ? 0 : childrenLength - 1; diff --git a/dist/system/aurelia-ui-virtualization.js b/dist/system/aurelia-ui-virtualization.js index 8825d7b..54bad09 100644 --- a/dist/system/aurelia-ui-virtualization.js +++ b/dist/system/aurelia-ui-virtualization.js @@ -1,21 +1,25 @@ 'use strict'; -System.register(['./virtual-repeat'], function (_export, _context) { +System.register(['./virtual-repeat', './virtual-repeat-next'], function (_export, _context) { "use strict"; - var VirtualRepeat; + var VirtualRepeat, VirtualRepeatNext; return { setters: [function (_virtualRepeat) { VirtualRepeat = _virtualRepeat.VirtualRepeat; + }, function (_virtualRepeatNext) { + VirtualRepeatNext = _virtualRepeatNext.VirtualRepeatNext; }], execute: function () { function configure(config) { - config.globalResources('./virtual-repeat'); + config.globalResources('./virtual-repeat', './virtual-repeat-next'); } _export('configure', configure); _export('VirtualRepeat', VirtualRepeat); + + _export('VirtualRepeatNext', VirtualRepeatNext); } }; }); \ No newline at end of file diff --git a/dist/system/template-strategy.js b/dist/system/template-strategy.js index 57e9a47..548a043 100644 --- a/dist/system/template-strategy.js +++ b/dist/system/template-strategy.js @@ -56,11 +56,13 @@ System.register(['aurelia-pal', 'aurelia-templating', './utilities'], function ( }; TableStrategy.prototype.moveViewFirst = function moveViewFirst(view, topBuffer) { - insertBeforeNode(view, DOM.nextElementSibling(topBuffer.parentNode).previousSibling); + insertBeforeNode(view, DOM.nextElementSibling(topBuffer.parentNode)); }; TableStrategy.prototype.moveViewLast = function moveViewLast(view, bottomBuffer) { - insertBeforeNode(view, bottomBuffer.parentNode); + var previousSibling = bottomBuffer.parentNode.previousSibling; + var referenceNode = previousSibling.nodeType === 8 && previousSibling.data === 'anchor' ? previousSibling : bottomBuffer.parentNode; + insertBeforeNode(view, referenceNode); }; TableStrategy.prototype.createTopBufferElement = function createTopBufferElement(element) { diff --git a/dist/system/virtual-repeat-next.js b/dist/system/virtual-repeat-next.js new file mode 100644 index 0000000..d0a6467 --- /dev/null +++ b/dist/system/virtual-repeat-next.js @@ -0,0 +1,32 @@ +'use strict'; + +System.register(['aurelia-templating'], function (_export, _context) { + "use strict"; + + var customAttribute, _dec, _class, VirtualRepeatNext; + + + + return { + setters: [function (_aureliaTemplating) { + customAttribute = _aureliaTemplating.customAttribute; + }], + execute: function () { + _export('VirtualRepeatNext', VirtualRepeatNext = (_dec = customAttribute('virtual-repeat-next'), _dec(_class = function () { + function VirtualRepeatNext() { + + } + + VirtualRepeatNext.prototype.attached = function attached() {}; + + VirtualRepeatNext.prototype.bind = function bind(bindingContext, overrideContext) { + this.scope = { bindingContext: bindingContext, overrideContext: overrideContext }; + }; + + return VirtualRepeatNext; + }()) || _class)); + + _export('VirtualRepeatNext', VirtualRepeatNext); + } + }; +}); \ No newline at end of file diff --git a/dist/system/virtual-repeat.js b/dist/system/virtual-repeat.js index 47228f4..06e33cf 100644 --- a/dist/system/virtual-repeat.js +++ b/dist/system/virtual-repeat.js @@ -3,7 +3,7 @@ System.register(['aurelia-dependency-injection', 'aurelia-binding', 'aurelia-templating', 'aurelia-templating-resources', 'aurelia-pal', './utilities', './dom-helper', './virtual-repeat-strategy-locator', './template-strategy'], function (_export, _context) { "use strict"; - var inject, ObserverLocator, BoundViewFactory, ViewSlot, ViewResources, TargetInstruction, customAttribute, bindable, templateController, View, AbstractRepeater, getItemsSourceExpression, isOneTime, unwrapExpression, updateOneTimeBinding, viewsRequireLifecycle, DOM, getStyleValue, calcOuterHeight, rebindAndMoveView, DomHelper, VirtualRepeatStrategyLocator, TemplateStrategyLocator, _dec, _dec2, _class, _desc, _value, _class2, _descriptor, _descriptor2, VirtualRepeat; + var inject, ObserverLocator, BoundViewFactory, ViewSlot, ViewResources, TargetInstruction, customAttribute, bindable, templateController, View, AbstractRepeater, getItemsSourceExpression, isOneTime, unwrapExpression, updateOneTimeBinding, viewsRequireLifecycle, DOM, getStyleValue, calcOuterHeight, rebindAndMoveView, DomHelper, VirtualRepeatStrategyLocator, TemplateStrategyLocator, _typeof, _dec, _dec2, _class, _desc, _value, _class2, _descriptor, _descriptor2, VirtualRepeat; function _initDefineProp(target, property, descriptor, context) { if (!descriptor) return; @@ -109,6 +109,12 @@ System.register(['aurelia-dependency-injection', 'aurelia-binding', 'aurelia-tem TemplateStrategyLocator = _templateStrategy.TemplateStrategyLocator; }], execute: function () { + _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { + return typeof obj; + } : function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; + }; + _export('VirtualRepeat', VirtualRepeat = (_dec = customAttribute('virtual-repeat'), _dec2 = inject(DOM.Element, BoundViewFactory, TargetInstruction, ViewSlot, ViewResources, ObserverLocator, VirtualRepeatStrategyLocator, TemplateStrategyLocator, DomHelper), _dec(_class = templateController(_class = _dec2(_class = (_class2 = function (_AbstractRepeater) { _inherits(VirtualRepeat, _AbstractRepeater); @@ -135,6 +141,7 @@ System.register(['aurelia-dependency-injection', 'aurelia-binding', 'aurelia-tem _this._fixedHeightContainer = false; _this._hasCalculatedSizes = false; _this._isAtTop = true; + _this._calledGetMore = false; _initDefineProp(_this, 'items', _descriptor, _this); @@ -308,6 +315,7 @@ System.register(['aurelia-dependency-injection', 'aurelia-binding', 'aurelia-tem this._lastRebind = this._first; var movedViewsCount = this._moveViews(viewsToMove); var adjustHeight = movedViewsCount < viewsToMove ? this._bottomBufferHeight : itemHeight * movedViewsCount; + this._getMore(); this._switchedDirection = false; this._topBufferHeight = this._topBufferHeight + adjustHeight; this._bottomBufferHeight = this._bottomBufferHeight - adjustHeight; @@ -340,6 +348,45 @@ System.register(['aurelia-dependency-injection', 'aurelia-binding', 'aurelia-tem this._ticking = false; }; + VirtualRepeat.prototype._getMore = function _getMore() { + var _this5 = this; + + if (this.isLastIndex) { + if (!this._calledGetMore) { + var _ret = function () { + var getMoreFunc = _this5.view(0).firstChild.getAttribute('virtual-repeat-next'); + if (!getMoreFunc) { + return { + v: void 0 + }; + } + var getMore = _this5.scope.overrideContext.bindingContext[getMoreFunc]; + + _this5.observerLocator.taskQueue.queueMicroTask(function () { + _this5._calledGetMore = true; + if (getMore instanceof Promise) { + return getMore.then(function () { + _this5._calledGetMore = false; + }); + } else if (typeof getMore === 'function') { + var result = getMore.bind(_this5.scope.overrideContext.bindingContext)(); + if (result instanceof Promise) { + return result.then(function () { + _this5._calledGetMore = false; + }); + } else { + _this5._calledGetMore = false; + return; + } + } + }); + }(); + + if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; + } + } + }; + VirtualRepeat.prototype._checkScrolling = function _checkScrolling() { if (this._first > this._previousFirst && (this._bottomBufferHeight > 0 || !this.isLastIndex)) { if (!this._scrollingDown) { @@ -378,7 +425,7 @@ System.register(['aurelia-dependency-injection', 'aurelia-binding', 'aurelia-tem }; VirtualRepeat.prototype._moveViews = function _moveViews(length) { - var _this5 = this; + var _this6 = this; var getNextIndex = this._scrollingDown ? function (index, i) { return index + i; @@ -386,7 +433,7 @@ System.register(['aurelia-dependency-injection', 'aurelia-binding', 'aurelia-tem return index - i; }; var isAtFirstOrLastIndex = function isAtFirstOrLastIndex() { - return _this5._scrollingDown ? _this5.isLastIndex : _this5._isAtTop; + return _this6._scrollingDown ? _this6.isLastIndex : _this6._isAtTop; }; var childrenLength = this.viewCount(); var viewIndex = this._scrollingDown ? 0 : childrenLength - 1; diff --git a/dist/temp/aurelia-ui-virtualization.js b/dist/temp/aurelia-ui-virtualization.js index 6129588..ea2c48d 100644 --- a/dist/temp/aurelia-ui-virtualization.js +++ b/dist/temp/aurelia-ui-virtualization.js @@ -3,9 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true }); -exports.VirtualRepeat = exports.VirtualRepeatStrategyLocator = exports.DefaultTemplateStrategy = exports.TableStrategy = exports.TemplateStrategyLocator = exports.ArrayVirtualRepeatStrategy = exports.DomHelper = undefined; +exports.VirtualRepeat = exports.VirtualRepeatStrategyLocator = exports.DefaultTemplateStrategy = exports.TableStrategy = exports.TemplateStrategyLocator = exports.ArrayVirtualRepeatStrategy = exports.VirtualRepeatNext = exports.DomHelper = undefined; -var _dec, _dec2, _class2, _desc, _value, _class3, _descriptor, _descriptor2; +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + +var _dec, _class, _dec2, _dec3, _class3, _desc, _value, _class4, _descriptor, _descriptor2; exports.calcOuterHeight = calcOuterHeight; exports.insertBeforeNode = insertBeforeNode; @@ -15,10 +17,10 @@ exports.getStyleValue = getStyleValue; exports.getElementDistanceToBottomViewPort = getElementDistanceToBottomViewPort; exports.getElementDistanceToTopViewPort = getElementDistanceToTopViewPort; -var _aureliaTemplatingResources = require('aurelia-templating-resources'); - var _aureliaTemplating = require('aurelia-templating'); +var _aureliaTemplatingResources = require('aurelia-templating-resources'); + var _aureliaPal = require('aurelia-pal'); var _aureliaDependencyInjection = require('aurelia-dependency-injection'); @@ -96,6 +98,19 @@ var DomHelper = exports.DomHelper = function () { return DomHelper; }(); +var VirtualRepeatNext = exports.VirtualRepeatNext = (_dec = (0, _aureliaTemplating.customAttribute)('virtual-repeat-next'), _dec(_class = function () { + function VirtualRepeatNext() { + _classCallCheck(this, VirtualRepeatNext); + } + + VirtualRepeatNext.prototype.attached = function attached() {}; + + VirtualRepeatNext.prototype.bind = function bind(bindingContext, overrideContext) { + this.scope = { bindingContext: bindingContext, overrideContext: overrideContext }; + }; + + return VirtualRepeatNext; +}()) || _class); function calcOuterHeight(element) { var height = void 0; height = element.getBoundingClientRect().height; @@ -465,11 +480,13 @@ var TableStrategy = exports.TableStrategy = function () { }; TableStrategy.prototype.moveViewFirst = function moveViewFirst(view, topBuffer) { - insertBeforeNode(view, _aureliaPal.DOM.nextElementSibling(topBuffer.parentNode).previousSibling); + insertBeforeNode(view, _aureliaPal.DOM.nextElementSibling(topBuffer.parentNode)); }; TableStrategy.prototype.moveViewLast = function moveViewLast(view, bottomBuffer) { - insertBeforeNode(view, bottomBuffer.parentNode); + var previousSibling = bottomBuffer.parentNode.previousSibling; + var referenceNode = previousSibling.nodeType === 8 && previousSibling.data === 'anchor' ? previousSibling : bottomBuffer.parentNode; + insertBeforeNode(view, referenceNode); }; TableStrategy.prototype.createTopBufferElement = function createTopBufferElement(element) { @@ -578,7 +595,7 @@ var VirtualRepeatStrategyLocator = exports.VirtualRepeatStrategyLocator = functi return VirtualRepeatStrategyLocator; }(_aureliaTemplatingResources.RepeatStrategyLocator); -var VirtualRepeat = exports.VirtualRepeat = (_dec = (0, _aureliaTemplating.customAttribute)('virtual-repeat'), _dec2 = (0, _aureliaDependencyInjection.inject)(_aureliaPal.DOM.Element, _aureliaTemplating.BoundViewFactory, _aureliaTemplating.TargetInstruction, _aureliaTemplating.ViewSlot, _aureliaTemplating.ViewResources, _aureliaBinding.ObserverLocator, VirtualRepeatStrategyLocator, TemplateStrategyLocator, DomHelper), _dec(_class2 = (0, _aureliaTemplating.templateController)(_class2 = _dec2(_class2 = (_class3 = function (_AbstractRepeater) { +var VirtualRepeat = exports.VirtualRepeat = (_dec2 = (0, _aureliaTemplating.customAttribute)('virtual-repeat'), _dec3 = (0, _aureliaDependencyInjection.inject)(_aureliaPal.DOM.Element, _aureliaTemplating.BoundViewFactory, _aureliaTemplating.TargetInstruction, _aureliaTemplating.ViewSlot, _aureliaTemplating.ViewResources, _aureliaBinding.ObserverLocator, VirtualRepeatStrategyLocator, TemplateStrategyLocator, DomHelper), _dec2(_class3 = (0, _aureliaTemplating.templateController)(_class3 = _dec3(_class3 = (_class4 = function (_AbstractRepeater) { _inherits(VirtualRepeat, _AbstractRepeater); function VirtualRepeat(element, viewFactory, instruction, viewSlot, viewResources, observerLocator, strategyLocator, templateStrategyLocator, domHelper) { @@ -604,6 +621,7 @@ var VirtualRepeat = exports.VirtualRepeat = (_dec = (0, _aureliaTemplating.custo _this5._fixedHeightContainer = false; _this5._hasCalculatedSizes = false; _this5._isAtTop = true; + _this5._calledGetMore = false; _initDefineProp(_this5, 'items', _descriptor, _this5); @@ -777,6 +795,7 @@ var VirtualRepeat = exports.VirtualRepeat = (_dec = (0, _aureliaTemplating.custo this._lastRebind = this._first; var movedViewsCount = this._moveViews(viewsToMove); var adjustHeight = movedViewsCount < viewsToMove ? this._bottomBufferHeight : itemHeight * movedViewsCount; + this._getMore(); this._switchedDirection = false; this._topBufferHeight = this._topBufferHeight + adjustHeight; this._bottomBufferHeight = this._bottomBufferHeight - adjustHeight; @@ -809,6 +828,45 @@ var VirtualRepeat = exports.VirtualRepeat = (_dec = (0, _aureliaTemplating.custo this._ticking = false; }; + VirtualRepeat.prototype._getMore = function _getMore() { + var _this9 = this; + + if (this.isLastIndex) { + if (!this._calledGetMore) { + var _ret2 = function () { + var getMoreFunc = _this9.view(0).firstChild.getAttribute('virtual-repeat-next'); + if (!getMoreFunc) { + return { + v: void 0 + }; + } + var getMore = _this9.scope.overrideContext.bindingContext[getMoreFunc]; + + _this9.observerLocator.taskQueue.queueMicroTask(function () { + _this9._calledGetMore = true; + if (getMore instanceof Promise) { + return getMore.then(function () { + _this9._calledGetMore = false; + }); + } else if (typeof getMore === 'function') { + var result = getMore.bind(_this9.scope.overrideContext.bindingContext)(); + if (result instanceof Promise) { + return result.then(function () { + _this9._calledGetMore = false; + }); + } else { + _this9._calledGetMore = false; + return; + } + } + }); + }(); + + if ((typeof _ret2 === 'undefined' ? 'undefined' : _typeof(_ret2)) === "object") return _ret2.v; + } + } + }; + VirtualRepeat.prototype._checkScrolling = function _checkScrolling() { if (this._first > this._previousFirst && (this._bottomBufferHeight > 0 || !this.isLastIndex)) { if (!this._scrollingDown) { @@ -847,7 +905,7 @@ var VirtualRepeat = exports.VirtualRepeat = (_dec = (0, _aureliaTemplating.custo }; VirtualRepeat.prototype._moveViews = function _moveViews(length) { - var _this9 = this; + var _this10 = this; var getNextIndex = this._scrollingDown ? function (index, i) { return index + i; @@ -855,7 +913,7 @@ var VirtualRepeat = exports.VirtualRepeat = (_dec = (0, _aureliaTemplating.custo return index - i; }; var isAtFirstOrLastIndex = function isAtFirstOrLastIndex() { - return _this9._scrollingDown ? _this9.isLastIndex : _this9._isAtTop; + return _this10._scrollingDown ? _this10.isLastIndex : _this10._isAtTop; }; var childrenLength = this.viewCount(); var viewIndex = this._scrollingDown ? 0 : childrenLength - 1; @@ -1014,10 +1072,10 @@ var VirtualRepeat = exports.VirtualRepeat = (_dec = (0, _aureliaTemplating.custo }; return VirtualRepeat; -}(_aureliaTemplatingResources.AbstractRepeater), (_descriptor = _applyDecoratedDescriptor(_class3.prototype, 'items', [_aureliaTemplating.bindable], { +}(_aureliaTemplatingResources.AbstractRepeater), (_descriptor = _applyDecoratedDescriptor(_class4.prototype, 'items', [_aureliaTemplating.bindable], { enumerable: true, initializer: null -}), _descriptor2 = _applyDecoratedDescriptor(_class3.prototype, 'local', [_aureliaTemplating.bindable], { +}), _descriptor2 = _applyDecoratedDescriptor(_class4.prototype, 'local', [_aureliaTemplating.bindable], { enumerable: true, initializer: null -})), _class3)) || _class2) || _class2) || _class2); \ No newline at end of file +})), _class4)) || _class3) || _class3) || _class3); \ No newline at end of file diff --git a/package.json b/package.json index eda0ff6..f9f4b3a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aurelia-ui-virtualization", - "version": "1.0.0-beta.1.0.0", + "version": "1.0.0-beta.1.0.1", "description": "A plugin that provides a virtualized repeater and other virtualization services.", "keywords": [ "aurelia", @@ -21,7 +21,6 @@ }, "jspm": { "registry": "npm", - "jspmPackage": true, "main": "aurelia-ui-virtualization", "format": "amd", "directories": { diff --git a/src/aurelia-ui-virtualization.js b/src/aurelia-ui-virtualization.js index f63630b..83af475 100644 --- a/src/aurelia-ui-virtualization.js +++ b/src/aurelia-ui-virtualization.js @@ -1,11 +1,14 @@ import {VirtualRepeat} from './virtual-repeat'; +import {VirtualRepeatNext} from './virtual-repeat-next'; export function configure(config) { config.globalResources( - './virtual-repeat' + './virtual-repeat', + './virtual-repeat-next' ); } export { - VirtualRepeat + VirtualRepeat, + VirtualRepeatNext }; diff --git a/src/virtual-repeat-next.js b/src/virtual-repeat-next.js new file mode 100644 index 0000000..3e3577a --- /dev/null +++ b/src/virtual-repeat-next.js @@ -0,0 +1,18 @@ +import {customAttribute} from 'aurelia-templating'; + +//Placeholder attribute to prohibit use of this attribute name in other places + +@customAttribute('virtual-repeat-next') +export class VirtualRepeatNext { + + constructor(){ + } + + attached(){ + } + + bind(bindingContext, overrideContext): void { + this.scope = { bindingContext, overrideContext }; + } + +} diff --git a/src/virtual-repeat.js b/src/virtual-repeat.js index 20ca9c3..8e9dcdb 100644 --- a/src/virtual-repeat.js +++ b/src/virtual-repeat.js @@ -47,6 +47,7 @@ export class VirtualRepeat extends AbstractRepeater { _fixedHeightContainer = false; _hasCalculatedSizes = false; _isAtTop = true; + _calledGetMore = false; @bindable items @bindable local @@ -226,6 +227,7 @@ export class VirtualRepeat extends AbstractRepeater { this._lastRebind = this._first; let movedViewsCount = this._moveViews(viewsToMove); let adjustHeight = movedViewsCount < viewsToMove ? this._bottomBufferHeight : itemHeight * movedViewsCount; + this._getMore(); this._switchedDirection = false; this._topBufferHeight = this._topBufferHeight + adjustHeight; this._bottomBufferHeight = this._bottomBufferHeight - adjustHeight; @@ -258,6 +260,38 @@ export class VirtualRepeat extends AbstractRepeater { this._ticking = false; } + _getMore(): void{ + if(this.isLastIndex){ + if(!this._calledGetMore){ + let getMoreFunc = this.view(0).firstChild.getAttribute('virtual-repeat-next'); + if(!getMoreFunc){ + //break down the boogie + return; + } + let getMore = this.scope.overrideContext.bindingContext[getMoreFunc]; + + this.observerLocator.taskQueue.queueMicroTask(() =>{ + this._calledGetMore = true; + if(getMore instanceof Promise){ + return getMore.then(() => { + this._calledGetMore = false; //Reset for the next time + }) + } else if (typeof getMore === 'function'){ + let result = getMore.bind(this.scope.overrideContext.bindingContext)(); + if(result instanceof Promise){ + return result.then(() => { + this._calledGetMore = false; //Reset for the next time + }) + } else { + this._calledGetMore = false; //Reset for the next time + return; + } + } + }); + } + } + } + _checkScrolling(): void { if (this._first > this._previousFirst && (this._bottomBufferHeight > 0 || !this.isLastIndex)) { if (!this._scrollingDown) { diff --git a/test/virtual-repeat-integration.spec.js b/test/virtual-repeat-integration.spec.js index 9f55f52..c4ee573 100644 --- a/test/virtual-repeat-integration.spec.js +++ b/test/virtual-repeat-integration.spec.js @@ -63,6 +63,50 @@ describe('VirtualRepeat Integration', () => { } } + function validateScrolledState() { + let views = virtualRepeat.viewSlot.children; + let expectedHeight = viewModel.items.length * itemHeight; + let topBufferHeight = virtualRepeat.topBuffer.getBoundingClientRect().height; + let bottomBufferHeight = virtualRepeat.bottomBuffer.getBoundingClientRect().height; + let renderedItemsHeight = views.length * itemHeight; + expect(topBufferHeight + renderedItemsHeight + bottomBufferHeight).toBe(expectedHeight); + + if(viewModel.items.length > views.length) { + expect(topBufferHeight + bottomBufferHeight).toBeGreaterThan(0); + } + + // validate contextual data + let startingLoc = viewModel.items.indexOf(views[0].bindingContext.item); + for (let i = startingLoc; i < views.length; i++) { + expect(views[i].bindingContext.item).toBe(viewModel.items[i]); + let overrideContext = views[i].overrideContext; + expect(overrideContext.parentOverrideContext.bindingContext).toBe(viewModel); + expect(overrideContext.bindingContext).toBe(views[i].bindingContext); + let first = i === 0; + let last = i === viewModel.items.length - 1; + let even = i % 2 === 0; + expect(overrideContext.$index).toBe(i); + expect(overrideContext.$first).toBe(first); + expect(overrideContext.$last).toBe(last); + expect(overrideContext.$middle).toBe(!first && !last); + expect(overrideContext.$odd).toBe(!even); + expect(overrideContext.$even).toBe(even); + } + } + + function validateScroll(done) { + let elem = document.getElementById('scrollContainer'); + let event = new Event('scroll'); + elem.scrollTop = elem.scrollHeight; + elem.dispatchEvent(event); + window.setTimeout(()=>{ + window.requestAnimationFrame(() => { + validateScrolledState(); + done(); + }); + }); + } + function validatePush(done) { viewModel.items.push('Foo'); nq(() => validateState()); @@ -309,4 +353,60 @@ describe('VirtualRepeat Integration', () => { create.then(() => validateSplice(done)); }); }); + + describe('infinite scroll', () =>{ + let vm; + beforeEach(() => { + items = []; + vm = { + items: items, + getNextPage: function(){ + let itemLength = this.items.length; + for(let i = 0; i < 100; ++i) { + let itemNum = itemLength + i; + this.items.push('item' + itemNum); + } + } + }; + for(let i = 0; i < 1000; ++i) { + items.push('item' + i); + } + + spyOn(vm, 'getNextPage').and.callThrough(); + + component = StageComponent + .withResources(['src/virtual-repeat', 'src/virtual-repeat-next']) + .inView(`
+
\${item}
+
`) + .boundTo(vm); + + create = component.create().then(() => { + virtualRepeat = component.sut; + viewModel = component.viewModel; + spyOn(virtualRepeat, '_onScroll').and.callThrough(); + }); + }); + + afterEach(() => { + component.cleanUp(); + }); + + it('handles scrolling', done => { + create.then(() => { + validateScroll(() => { + expect(virtualRepeat._onScroll).toHaveBeenCalled(); + done(); + }) + }); + }) + it('handles getting next data set', done => { + create.then(() => { + validateScroll(() => { + expect(vm.getNextPage).toHaveBeenCalled(); + done(); + }) + }); + }) + }) });