Skip to content

Commit

Permalink
[RELEASE]: 0.8.0
Browse files Browse the repository at this point in the history
  • Loading branch information
orizens committed Jan 2, 2018
1 parent e487d72 commit 269b5f6
Show file tree
Hide file tree
Showing 12 changed files with 185 additions and 86 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## v 0.8.0 (2018/01/02)
* [FIX] - now triggers only once when in or after target (#200)
* [REFACTOR] - "distance" number has been refined to be the percentage point of the scroll nob.
* [REFACTOR] - added more unit tests.

## v 0.7.2 (2017/12/07)
* [FIX] - fix for ie11 - fix #157

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ npm install ngx-infinite-scroll --save

## Supported API
Currently supported attributes:
* **infiniteScrollDistance**<_number_> - (optional, default: **2**) - should get a number, the number of viewport lengths from the bottom of the page at which the event will be triggered.
* **infiniteScrollDistance**<_number_> - (optional, default: **2**) - the bottom percentage point of the scroll nob relatively to the infinite-scroll container (i.e, 2 (2 * 10 = 20%) is event is triggered when 80% (100% - 20%) has been scrolled).
if container.height is 900px, when the container is scrolled to or past the 720px, it will fire the scrolled event.
* **infiniteScrollUpDistance**<_number_> - (optional, default: **1.5**) - should get a number
* **infiniteScrollThrottle**<_number_> - (optional, default: **300**) - should get a number of **milliseconds** for throttle. The event will be triggered this many milliseconds after the user *stops* scrolling.
* **infiniteScrollContainer**<_string|HTMLElement_> - (optional, default: null) - should get a html element or css selector for a scrollable element; window or current element will be used if this attribute is empty.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ngx-infinite-scroll",
"version": "0.7.2",
"version": "0.8.0",
"description": "An infinite scroll directive for Angular compatible with AoT compilation and Tree shaking",
"main": "./bundles/ngx-infinite-scroll.umd.js",
"module": "./modules/ngx-infinite-scroll.es5.js",
Expand Down
8 changes: 7 additions & 1 deletion src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ export interface InfiniteScrollEvent {
export interface IPositionElements {
windowElement: ContainerRef;
axis: any;
isWindow: boolean;
}

export interface IPositionStats {
Expand All @@ -31,6 +30,13 @@ export interface IScrollStats {
shouldScroll: boolean;
}

export interface IScrollState {
lastTotalToScroll: number;
totalToScroll: number;
isTriggeredTotal: boolean;
lastScrollPosition: number;
}

export interface IResolver {
container: ContainerRef;
isWindow: boolean;
Expand Down
11 changes: 3 additions & 8 deletions src/services/event-trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,11 @@ export interface IDistanceRange {

export interface IScrollConfig {
alwaysCallback: boolean;
disable: boolean;
shouldScroll: boolean;
shouldFireScrollEvent: boolean;
}

export function shouldTriggerEvents({
alwaysCallback,
shouldScroll,
disable
}: IScrollConfig) {
return (alwaysCallback || shouldScroll) && !disable;
export function shouldTriggerEvents({ alwaysCallback, shouldFireScrollEvent }: IScrollConfig) {
return (alwaysCallback || shouldFireScrollEvent);
}

export function triggerEvents(
Expand Down
6 changes: 1 addition & 5 deletions src/services/position-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,11 @@ import { ContainerRef, IPositionElements, IPositionStats, IResolver } from '../m
import { AxisResolver } from './axis-resolver';

export function createResolver({
isWindow,
windowElement,
axis
}: IPositionElements): IResolver {
return createResolverWithContainer(
{
axis,
isWindow
},
{ axis, isWindow: isElementWindow(windowElement) },
windowElement
);
}
Expand Down
66 changes: 32 additions & 34 deletions src/services/scroll-register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import { ElementRef } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';

import { ContainerRef, IPositionStats } from '../models';
import { ContainerRef, IPositionStats, IScrollState } from '../models';
import { AxisResolver } from './axis-resolver';
import { shouldTriggerEvents, triggerEvents } from './event-trigger';
import { resolveContainerElement } from './ngx-ins-utils';
import { calculatePoints, createResolver, isElementWindow } from './position-resolver';
import { getScrollStats, updateScrollPosition } from './scroll-resolver';
import { calculatePoints, createResolver } from './position-resolver';
import * as ScrollResolver from './scroll-resolver';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';

export interface IScrollRegisterConfig {
container: ContainerRef;
Expand All @@ -39,63 +40,60 @@ export interface IScroller {
};
}

export function attachScrollEvent(
options: IScrollRegisterConfig
): Subscription {
return Observable.fromEvent(options.container, 'scroll')
.sampleTime(options.throttleDuration)
.mergeMap((ev: any) => Observable.of(options.mergeMap(ev)))
.subscribe(options.scrollHandler);
}

export function createScroller(config: IScroller): Subscription {
const containerElement = resolveContainerElement(
config.scrollContainer,
config.scrollWindow,
config.element,
config.fromRoot
);
const { scrollContainer, scrollWindow, element, fromRoot } = config;
const resolver = createResolver({
axis: new AxisResolver(!config.horizontal),
isWindow: isElementWindow(containerElement),
windowElement: containerElement
windowElement: resolveContainerElement(scrollContainer, scrollWindow, element, fromRoot)
});
const scrollPosition = {
last: 0
const stats = calculatePoints(element, resolver);
const scrollState: IScrollState = {
lastScrollPosition: 0,
lastTotalToScroll: 0,
totalToScroll: stats.totalToScroll,
isTriggeredTotal: false
};
const options: IScrollRegisterConfig = {
container: resolver.container,
mergeMap: () => calculatePoints(config.element, resolver),
mergeMap: () => calculatePoints(element, resolver),
scrollHandler: (positionStats: IPositionStats) =>
handleOnScroll(scrollPosition, positionStats, config),
handleOnScroll(scrollState, positionStats, config),
throttleDuration: config.throttle
};
return attachScrollEvent(options);
}

export function attachScrollEvent(
options: IScrollRegisterConfig
): Subscription {
return Observable.fromEvent(options.container, 'scroll')
.sampleTime(options.throttleDuration)
.mergeMap((ev: any) => Observable.of(options.mergeMap(ev)))
.subscribe(options.scrollHandler);
}

export function handleOnScroll(
scrollPosition,
scrollState: IScrollState,
positionStats: IPositionStats,
config: IScroller
) {
const distance = {
down: config.downDistance,
up: config.upDistance
};
const { isScrollingDown, shouldScroll } = getScrollStats(
scrollPosition.last,
const { isScrollingDown, shouldFireScrollEvent } = ScrollResolver.getScrollStats(
scrollState.lastScrollPosition,
positionStats,
{
distance
}
{ distance }
);
const scrollConfig = {
alwaysCallback: config.alwaysCallback,
disable: config.disable,
shouldScroll
shouldFireScrollEvent
};
updateScrollPosition(positionStats.scrolledUntilNow, scrollPosition);
if (shouldTriggerEvents(scrollConfig)) {
ScrollResolver.updateScrollState(scrollState, positionStats.scrolledUntilNow, positionStats.totalToScroll);
const shouldTrigger = shouldTriggerEvents(scrollConfig);
if (shouldTrigger && !scrollState.isTriggeredTotal) {
ScrollResolver.updateTriggeredFlag(scrollState, true);
triggerEvents(
config.events,
isScrollingDown,
Expand Down
45 changes: 34 additions & 11 deletions src/services/scroll-resolver.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IPositionStats, IScrollerConfig } from '../models';
import { IPositionStats, IScrollerConfig, IScrollState } from '../models';

export function shouldScroll(
export function shouldFireScrollEvent(
container: IPositionStats,
config: IScrollerConfig,
scrollingDown: boolean
Expand All @@ -9,14 +9,15 @@ export function shouldScroll(
let remaining: number;
let containerBreakpoint: number;
if (scrollingDown) {
remaining = container.totalToScroll - container.scrolledUntilNow;
containerBreakpoint = container.height * distance.down + 1;
remaining = (container.totalToScroll - container.scrolledUntilNow) / container.totalToScroll;
containerBreakpoint = distance.down / 10;
} else {
remaining = container.scrolledUntilNow;
containerBreakpoint = container.height * distance.up + 1;
remaining = container.scrolledUntilNow / container.totalToScroll;
containerBreakpoint = distance.up / 10;
}
const shouldScroll: boolean = remaining <= containerBreakpoint;
return shouldScroll;

const shouldFireEvent: boolean = remaining <= containerBreakpoint;
return shouldFireEvent;
}

export function isScrollingDownwards(
Expand All @@ -33,11 +34,33 @@ export function getScrollStats(
) {
const isScrollingDown = isScrollingDownwards(lastScrollPosition, container);
return {
shouldScroll: shouldScroll(container, config, isScrollingDown),
shouldFireScrollEvent: shouldFireScrollEvent(container, config, isScrollingDown),
isScrollingDown
};
}

export function updateScrollPosition(position: number, lastPositionState) {
return (lastPositionState.last = position);
export function updateScrollPosition(position: number, scrollState: IScrollState) {
return (scrollState.lastScrollPosition = position);
}

export function updateTotalToScroll(totalToScroll: number, scrollState: IScrollState) {
scrollState.lastTotalToScroll = scrollState.totalToScroll;
scrollState.totalToScroll = totalToScroll;
}

export function isSameTotalToScroll(scrollState) {
return scrollState.totalToScroll === scrollState.lastTotalToScroll;
}

export function updateTriggeredFlag(scrollState, triggered: boolean) {
scrollState.isTriggeredTotal = triggered;
}

export function updateScrollState(scrollState: IScrollState, scrolledUntilNow: number, totalToScroll: number) {
updateScrollPosition(scrolledUntilNow, scrollState);
updateTotalToScroll(totalToScroll, scrollState);
const isSameTotal = isSameTotalToScroll(scrollState);
if (!isSameTotal) {
updateTriggeredFlag(scrollState, false);
}
}
29 changes: 12 additions & 17 deletions tests/services/event-trigger.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,49 +9,44 @@ const props = {
} as IScrollerProps;

describe('EventTrigger', () => {
it('should return true when alwaysCallback, not disabled', () => {
it('should return true when alwaysCallback', () => {
const actual = shouldTriggerEvents({
alwaysCallback: true,
shouldScroll: false,
disable: false
shouldFireScrollEvent: false,
});
expect(actual).toBeTruthy();
});

it('should return true when alwaysCallback, shouldScroll and not disabled', () => {
it('should return true when alwaysCallback, shouldFireScrollEvent', () => {
const actual = shouldTriggerEvents({
alwaysCallback: true,
shouldScroll: true,
disable: false
shouldFireScrollEvent: true,
});
expect(actual).toBeTruthy();
});

it('should return true when not alwaysCallback, shouldScroll is true and not disabled', () => {
it('should return true when not alwaysCallback, shouldFireScrollEvent is true', () => {
const actual = shouldTriggerEvents({
alwaysCallback: false,
shouldScroll: true,
disable: false
shouldFireScrollEvent: true,
});
expect(actual).toBeTruthy();
});

it('should return false when alwaysCallback, shouldScroll is true and disabled', () => {
it('should return false when alwaysCallback, shouldFireScrollEvent is true', () => {
const actual = shouldTriggerEvents({
alwaysCallback: true,
shouldScroll: true,
disable: true
shouldFireScrollEvent: true,
});
expect(actual).toBeFalsy();
expect(actual).toBeTruthy();
});

it('should return false when not alwaysCallback, shouldScroll is true and disabled', () => {
it('should return false when not alwaysCallback, shouldFireScrollEvent is true', () => {
const actual = shouldTriggerEvents({
alwaysCallback: false,
shouldScroll: true,
disable: true
shouldFireScrollEvent: true,
});
expect(actual).toBeFalsy();
expect(actual).toBeTruthy();
});

describe('triggerEvents', () => {
Expand Down
1 change: 0 additions & 1 deletion tests/services/position-resolver.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ describe('Position Resolver', () => {
const actual = createResolver({
axis,
windowElement: mockDom.element,
isWindow: true
});
expect(actual).toBeDefined();
});
Expand Down
Loading

0 comments on commit 269b5f6

Please sign in to comment.