Skip to content

Commit

Permalink
fix(repeat): add sizing handler
Browse files Browse the repository at this point in the history
  • Loading branch information
bigopon committed Mar 19, 2019
1 parent 3c62cfc commit 7f41efa
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 178 deletions.
52 changes: 27 additions & 25 deletions src/array-virtual-repeat-strategy.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ICollectionObserverSplice, mergeSplice } from 'aurelia-binding';
import { ViewSlot } from 'aurelia-templating';
import { ArrayRepeatStrategy, createFullOverrideContext } from 'aurelia-templating-resources';
import { IView, IVirtualRepeatStrategy } from './interfaces';
import { IView, IVirtualRepeatStrategy, VirtualizationCalculation } from './interfaces';
import {
Math$abs,
Math$floor,
Expand All @@ -22,12 +22,12 @@ export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy implements I
return repeat.addView(overrideContext.bindingContext, overrideContext);
}

initCalculation(repeat: VirtualRepeat, items: any[]): boolean {
initCalculation(repeat: VirtualRepeat, items: any[]): VirtualizationCalculation {
const itemCount = items.length;
// when there is no item, bails immediately
// and return false to notify calculation finished unsuccessfully
if (!(itemCount > 0)) {
return false;
return VirtualizationCalculation.reset;
}
// before invoking instance changed, there needs to be basic calculation on how
// the required vairables such as item height and elements required
Expand All @@ -38,19 +38,21 @@ export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy implements I
}
const isFixedHeightContainer = repeat._fixedHeightContainer = hasOverflowScroll(containerEl);
const firstView = repeat._firstView();
const itemHeight = repeat.itemHeight = calcOuterHeight(firstView.firstChild as Element);
const itemHeight = calcOuterHeight(firstView.firstChild as Element);
// when item height is 0, bails immediately
// and return false to notify calculation has finished unsuccessfully
// it cannot be processed further when item is 0
if (itemHeight === 0) {
return false;
return VirtualizationCalculation.none;
}
repeat.itemHeight = itemHeight;
const scroll_el_height = isFixedHeightContainer
? calcScrollHeight(containerEl)
: document.documentElement.clientHeight;
// console.log({ scroll_el_height })
const elementsInView = repeat.elementsInView = Math$floor(scroll_el_height / itemHeight) + 1;
const viewsCount = repeat._viewsLength = elementsInView * 2;
return true;
return VirtualizationCalculation.has_sizing | VirtualizationCalculation.observe_scroller;
}

/**
Expand Down Expand Up @@ -88,8 +90,7 @@ export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy implements I
if (currItemCount === 0) {
repeat.removeAllViews(/*return to cache?*/true, /*skip animation?*/false);
repeat._resetCalculation();
delete repeat.__queuedSplices;
delete repeat.__array;
repeat.__queuedSplices = repeat.__array = undefined;
return false;
}
/*
Expand All @@ -99,26 +100,32 @@ export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy implements I
To figure out that one, we're going to have to know where we are in our scrolling so we can know how far down we've gone to show the first view
That "first" is calculated and passed into here
*/
// remove unneeded views.

// if the number of items shrinks to less than number of active views
// remove all unneeded views
let realViewsCount = repeat.viewCount();
// console.log('0. Start inplace process item', { realViewsCount, currItemCount, els: repeat.elementsInView, max: repeat._viewsLength })
while (realViewsCount > currItemCount) {
realViewsCount--;
repeat.removeView(realViewsCount, /*return to cache?*/true, /*skip animation?*/false);
}
// console.log(realViewsCount, repeat._viewsLength)
// there is situation when container height shrinks
// the real views count will be greater than new maximum required view count
// remove all unnecessary view
while (realViewsCount > repeat._viewsLength) {
realViewsCount--;
repeat.removeView(realViewsCount, /*return to cache?*/true, /*skip animation?*/false);
}
realViewsCount = Math$min(realViewsCount, repeat._viewsLength);

const local = repeat.local;
const lastIndex = currItemCount - 1;

// console.log('1. firstIndex, realCount, lastIndex', { firstIndex, realViewsCount, lastIndex })
if (firstIndex + realViewsCount > lastIndex) {
// first = currItemCount - realViewsCount instead of: first = currItemCount - 1 - realViewsCount;
// this is because during view update
// view(i) starts at 0 and ends at less than last
firstIndex = Math$max(0, currItemCount - realViewsCount);
}
// console.log('2. firstIndex, realCount, lastIndex', { firstIndex, realViewsCount, lastIndex })

repeat._first = firstIndex;
// re-evaluate bindings on existing views.
Expand Down Expand Up @@ -151,9 +158,6 @@ export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy implements I
}
// add new views
const minLength = Math$min(repeat._viewsLength, currItemCount);
// console.log('4. After updating existing views',
// { firstIndex, minLength, maxView: repeat._viewsLength, currItemCount, realViewsCount }
// )
for (let i = realViewsCount; i < minLength; i++) {
const overrideContext = createFullOverrideContext(repeat, items[i], i, currItemCount);
repeat.addView(overrideContext.bindingContext, overrideContext);
Expand All @@ -165,7 +169,7 @@ export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy implements I
_standardProcessInstanceMutated(repeat: VirtualRepeat, array: Array<any>, splices: ICollectionObserverSplice[]): void {
if (repeat.__queuedSplices) {
for (let i = 0, ii = splices.length; i < ii; ++i) {
let {index, removed, addedCount} = splices[i];
const { index, removed, addedCount } = splices[i];
mergeSplice(repeat.__queuedSplices, index, removed, addedCount);
}
repeat.__array = array.slice(0);
Expand All @@ -174,23 +178,21 @@ export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy implements I
if (array.length === 0) {
repeat.removeAllViews(/*return to cache?*/true, /*skip animation?*/false);
repeat._resetCalculation();
delete repeat.__queuedSplices;
delete repeat.__array;
repeat.__queuedSplices = repeat.__array = undefined;
return;
}

let maybePromise = this._runSplices(repeat, array.slice(0), splices);
const maybePromise = this._runSplices(repeat, array.slice(0), splices);
if (maybePromise instanceof Promise) {
let queuedSplices = repeat.__queuedSplices = [];
const queuedSplices = repeat.__queuedSplices = [];

let runQueuedSplices = () => {
const runQueuedSplices = () => {
if (! queuedSplices.length) {
delete repeat.__queuedSplices;
delete repeat.__array;
repeat.__queuedSplices = repeat.__array = undefined;
return;
}

let nextPromise = this._runSplices(repeat, repeat.__array, queuedSplices) || Promise.resolve();
const nextPromise = this._runSplices(repeat, repeat.__array, queuedSplices) || Promise.resolve();
nextPromise.then(runQueuedSplices);
};

Expand Down
3 changes: 2 additions & 1 deletion src/aurelia-ui-virtualization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ export {
};

export {
IScrollNextScrollContext
IScrollNextScrollContext,
VirtualizationEvents
} from './interfaces';
20 changes: 19 additions & 1 deletion src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export interface IVirtualRepeatStrategy extends RepeatStrategy {
*
* @returns `false` to notify that calculation hasn't been finished
*/
initCalculation(repeat: VirtualRepeat, items: number | any[] | Map<any, any> | Set<any>): boolean;
initCalculation(repeat: VirtualRepeat, items: number | any[] | Map<any, any> | Set<any>): VirtualizationCalculation;

/**
* Get the observer based on collection type of `items`
Expand Down Expand Up @@ -151,6 +151,24 @@ export interface IScrollerInfo {
height: number;
}

export const enum VirtualizationCalculation {
none = 0b0_00000,
reset = 0b0_00001,
has_sizing = 0b0_00010,
observe_scroller = 0b0_00100
}

/**
* List of events that can be used to notify virtual repeat that size has changed
*/
export const VirtualizationEvents = Object.assign(Object.create(null), {
scrollerSizeChange: 'virtual-repeat-scroller-size-changed' as 'virtual-repeat-scroller-size-changed',
itemSizeChange: 'virtual-repeat-item-size-changed' as 'virtual-repeat-item-size-changed'
}) as {
scrollerSizeChange: 'virtual-repeat-scroller-size-changed';
itemSizeChange: 'virtual-repeat-item-size-changed';
};

// export const enum IVirtualRepeatState {
// isAtTop = 0b0_000000_000,
// isLastIndex = 0b0_000000_000,
Expand Down
12 changes: 6 additions & 6 deletions src/null-virtual-repeat-strategy.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { NullRepeatStrategy, RepeatStrategy } from 'aurelia-templating-resources';
import { VirtualRepeat } from './virtual-repeat';
import { IVirtualRepeatStrategy, IView } from './interfaces';
import { IVirtualRepeatStrategy, IView, VirtualizationCalculation } from './interfaces';

export class NullVirtualRepeatStrategy extends NullRepeatStrategy implements IVirtualRepeatStrategy {

initCalculation(repeat: VirtualRepeat, items: any) {
repeat.elementsInView
= repeat.itemHeight
initCalculation(repeat: VirtualRepeat, items: any): VirtualizationCalculation {
repeat.itemHeight
= repeat.elementsInView
= repeat._viewsLength = 0;
// null/undefined virtual repeat strategy does not require any calculation
// returning true to signal that
return true;
// returning has_sizing to signal that
return VirtualizationCalculation.has_sizing;
}

// a violation of base contract, won't work in strict mode
Expand Down
5 changes: 5 additions & 0 deletions src/utilities-dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ export const insertBeforeNode = (view: IView, bottomBuffer: Element): void => {
* There are steps in the middle to account for offsetParent but it's basically that
*/
export const getDistanceToParent = (child: HTMLElement, parent: HTMLElement) => {
// optimizable case where child is the first child of parent
// and parent is the target parent to calculate
if (child.previousSibling === null && child.parentNode === parent) {
return 0;
}
const offsetParent = child.offsetParent as HTMLElement;
const childOffsetTop = child.offsetTop;
// [el] <-- offset parent === parent
Expand Down
Loading

0 comments on commit 7f41efa

Please sign in to comment.