Skip to content

Commit

Permalink
feat(infinite): add scroll in opposite direction (#8099)
Browse files Browse the repository at this point in the history
* feat(infinite): add scroll in opposite direction

fixes

* test(infinite-scroll): opposite direction e2e test

* fix(infinite-scroll): keep scroll position

* feat(content): scroll down on load

* fix(infinite-scroll): scroll the content down on load

* Requested changes
  • Loading branch information
Manduro authored and manucorporat committed Mar 15, 2017
1 parent f9f9a1b commit 6918275
Show file tree
Hide file tree
Showing 7 changed files with 298 additions and 10 deletions.
22 changes: 21 additions & 1 deletion src/components/content/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ export class Content extends Ion implements OnDestroy, OnInit {
_viewCtrlReadSub: any;
/** @internal */
_viewCtrlWriteSub: any;
/** @internal */
_scrollDownOnLoad: boolean = false;

private _imgReqBfr: number;
private _imgRndBfr: number;
Expand Down Expand Up @@ -478,13 +480,25 @@ export class Content extends Ion implements OnDestroy, OnInit {
*/
@Input()
get fullscreen(): boolean {
return !!this._fullscreen;
return this._fullscreen;
}

set fullscreen(val: boolean) {
this._fullscreen = isTrueProperty(val);
}

/**
* @input {boolean} If true, the content will scroll down on load.
*/
@Input()
get scrollDownOnLoad(): boolean {
return this._scrollDownOnLoad;
}

set scrollDownOnLoad(val: boolean) {
this._scrollDownOnLoad = isTrueProperty(val);
}

/**
* @private
*/
Expand Down Expand Up @@ -830,6 +844,12 @@ export class Content extends Ion implements OnDestroy, OnInit {
this._tabs.setTabbarPosition(-1, 0);
}
}

// Scroll the page all the way down after setting dimensions
if (this._scrollDownOnLoad) {
this.scrollToBottom(0);
this._scrollDownOnLoad = false;
}
}

/**
Expand Down
32 changes: 32 additions & 0 deletions src/components/content/test/scroll-down-on-load/app.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Component, NgModule } from '@angular/core';
import { IonicApp, IonicModule } from '../../../../../ionic-angular';


@Component({
templateUrl: 'main.html'
})
export class E2EPage {}


@Component({
template: '<ion-nav [root]="root"></ion-nav>'
})
export class E2EApp {
root = E2EPage;
}

@NgModule({
declarations: [
E2EApp,
E2EPage,
],
imports: [
IonicModule.forRoot(E2EApp)
],
bootstrap: [IonicApp],
entryComponents: [
E2EApp,
E2EPage,
]
})
export class AppModule {}
40 changes: 40 additions & 0 deletions src/components/content/test/scroll-down-on-load/main.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<ion-content scrollDownOnLoad="true">
<b>This page should scroll down on load</b>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi scelerisque dolor lacus, ut vehicula arcu dapibus id. Morbi iaculis fermentum blandit. Curabitur tempus, ante et vehicula tempor, urna velit rutrum massa, quis suscipit purus lacus eget est. Sed nisi nulla, tempus id dictum a, cursus ut felis. Aliquam orci magna, rutrum nec tempor ac, fermentum quis eros. Sed ullamcorper felis sit amet tristique sagittis. Nullam sed tempus mi. Morbi sit amet lacinia leo. Nunc facilisis orci id consectetur dignissim. Integer dictum consectetur enim. Vivamus auctor, turpis ut eleifend pharetra, purus magna mattis arcu, vel pharetra tellus orci eget ex. Integer blandit posuere vehicula. Ut ipsum lorem, efficitur vitae eleifend tincidunt, fermentum nec lacus. Ut nec fermentum dui.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi scelerisque dolor lacus, ut vehicula arcu dapibus id. Morbi iaculis fermentum blandit. Curabitur tempus, ante et vehicula tempor, urna velit rutrum massa, quis suscipit purus lacus eget est. Sed nisi nulla, tempus id dictum a, cursus ut felis. Aliquam orci magna, rutrum nec tempor ac, fermentum quis eros. Sed ullamcorper felis sit amet tristique sagittis. Nullam sed tempus mi. Morbi sit amet lacinia leo. Nunc facilisis orci id consectetur dignissim. Integer dictum consectetur enim. Vivamus auctor, turpis ut eleifend pharetra, purus magna mattis arcu, vel pharetra tellus orci eget ex. Integer blandit posuere vehicula. Ut ipsum lorem, efficitur vitae eleifend tincidunt, fermentum nec lacus. Ut nec fermentum dui.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi scelerisque dolor lacus, ut vehicula arcu dapibus id. Morbi iaculis fermentum blandit. Curabitur tempus, ante et vehicula tempor, urna velit rutrum massa, quis suscipit purus lacus eget est. Sed nisi nulla, tempus id dictum a, cursus ut felis. Aliquam orci magna, rutrum nec tempor ac, fermentum quis eros. Sed ullamcorper felis sit amet tristique sagittis. Nullam sed tempus mi. Morbi sit amet lacinia leo. Nunc facilisis orci id consectetur dignissim. Integer dictum consectetur enim. Vivamus auctor, turpis ut eleifend pharetra, purus magna mattis arcu, vel pharetra tellus orci eget ex. Integer blandit posuere vehicula. Ut ipsum lorem, efficitur vitae eleifend tincidunt, fermentum nec lacus. Ut nec fermentum dui.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi scelerisque dolor lacus, ut vehicula arcu dapibus id. Morbi iaculis fermentum blandit. Curabitur tempus, ante et vehicula tempor, urna velit rutrum massa, quis suscipit purus lacus eget est. Sed nisi nulla, tempus id dictum a, cursus ut felis. Aliquam orci magna, rutrum nec tempor ac, fermentum quis eros. Sed ullamcorper felis sit amet tristique sagittis. Nullam sed tempus mi. Morbi sit amet lacinia leo. Nunc facilisis orci id consectetur dignissim. Integer dictum consectetur enim. Vivamus auctor, turpis ut eleifend pharetra, purus magna mattis arcu, vel pharetra tellus orci eget ex. Integer blandit posuere vehicula. Ut ipsum lorem, efficitur vitae eleifend tincidunt, fermentum nec lacus. Ut nec fermentum dui.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi scelerisque dolor lacus, ut vehicula arcu dapibus id. Morbi iaculis fermentum blandit. Curabitur tempus, ante et vehicula tempor, urna velit rutrum massa, quis suscipit purus lacus eget est. Sed nisi nulla, tempus id dictum a, cursus ut felis. Aliquam orci magna, rutrum nec tempor ac, fermentum quis eros. Sed ullamcorper felis sit amet tristique sagittis. Nullam sed tempus mi. Morbi sit amet lacinia leo. Nunc facilisis orci id consectetur dignissim. Integer dictum consectetur enim. Vivamus auctor, turpis ut eleifend pharetra, purus magna mattis arcu, vel pharetra tellus orci eget ex. Integer blandit posuere vehicula. Ut ipsum lorem, efficitur vitae eleifend tincidunt, fermentum nec lacus. Ut nec fermentum dui.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi scelerisque dolor lacus, ut vehicula arcu dapibus id. Morbi iaculis fermentum blandit. Curabitur tempus, ante et vehicula tempor, urna velit rutrum massa, quis suscipit purus lacus eget est. Sed nisi nulla, tempus id dictum a, cursus ut felis. Aliquam orci magna, rutrum nec tempor ac, fermentum quis eros. Sed ullamcorper felis sit amet tristique sagittis. Nullam sed tempus mi. Morbi sit amet lacinia leo. Nunc facilisis orci id consectetur dignissim. Integer dictum consectetur enim. Vivamus auctor, turpis ut eleifend pharetra, purus magna mattis arcu, vel pharetra tellus orci eget ex. Integer blandit posuere vehicula. Ut ipsum lorem, efficitur vitae eleifend tincidunt, fermentum nec lacus. Ut nec fermentum dui.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi scelerisque dolor lacus, ut vehicula arcu dapibus id. Morbi iaculis fermentum blandit. Curabitur tempus, ante et vehicula tempor, urna velit rutrum massa, quis suscipit purus lacus eget est. Sed nisi nulla, tempus id dictum a, cursus ut felis. Aliquam orci magna, rutrum nec tempor ac, fermentum quis eros. Sed ullamcorper felis sit amet tristique sagittis. Nullam sed tempus mi. Morbi sit amet lacinia leo. Nunc facilisis orci id consectetur dignissim. Integer dictum consectetur enim. Vivamus auctor, turpis ut eleifend pharetra, purus magna mattis arcu, vel pharetra tellus orci eget ex. Integer blandit posuere vehicula. Ut ipsum lorem, efficitur vitae eleifend tincidunt, fermentum nec lacus. Ut nec fermentum dui.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi scelerisque dolor lacus, ut vehicula arcu dapibus id. Morbi iaculis fermentum blandit. Curabitur tempus, ante et vehicula tempor, urna velit rutrum massa, quis suscipit purus lacus eget est. Sed nisi nulla, tempus id dictum a, cursus ut felis. Aliquam orci magna, rutrum nec tempor ac, fermentum quis eros. Sed ullamcorper felis sit amet tristique sagittis. Nullam sed tempus mi. Morbi sit amet lacinia leo. Nunc facilisis orci id consectetur dignissim. Integer dictum consectetur enim. Vivamus auctor, turpis ut eleifend pharetra, purus magna mattis arcu, vel pharetra tellus orci eget ex. Integer blandit posuere vehicula. Ut ipsum lorem, efficitur vitae eleifend tincidunt, fermentum nec lacus. Ut nec fermentum dui.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi scelerisque dolor lacus, ut vehicula arcu dapibus id. Morbi iaculis fermentum blandit. Curabitur tempus, ante et vehicula tempor, urna velit rutrum massa, quis suscipit purus lacus eget est. Sed nisi nulla, tempus id dictum a, cursus ut felis. Aliquam orci magna, rutrum nec tempor ac, fermentum quis eros. Sed ullamcorper felis sit amet tristique sagittis. Nullam sed tempus mi. Morbi sit amet lacinia leo. Nunc facilisis orci id consectetur dignissim. Integer dictum consectetur enim. Vivamus auctor, turpis ut eleifend pharetra, purus magna mattis arcu, vel pharetra tellus orci eget ex. Integer blandit posuere vehicula. Ut ipsum lorem, efficitur vitae eleifend tincidunt, fermentum nec lacus. Ut nec fermentum dui.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi scelerisque dolor lacus, ut vehicula arcu dapibus id. Morbi iaculis fermentum blandit. Curabitur tempus, ante et vehicula tempor, urna velit rutrum massa, quis suscipit purus lacus eget est. Sed nisi nulla, tempus id dictum a, cursus ut felis. Aliquam orci magna, rutrum nec tempor ac, fermentum quis eros. Sed ullamcorper felis sit amet tristique sagittis. Nullam sed tempus mi. Morbi sit amet lacinia leo. Nunc facilisis orci id consectetur dignissim. Integer dictum consectetur enim. Vivamus auctor, turpis ut eleifend pharetra, purus magna mattis arcu, vel pharetra tellus orci eget ex. Integer blandit posuere vehicula. Ut ipsum lorem, efficitur vitae eleifend tincidunt, fermentum nec lacus. Ut nec fermentum dui.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi scelerisque dolor lacus, ut vehicula arcu dapibus id. Morbi iaculis fermentum blandit. Curabitur tempus, ante et vehicula tempor, urna velit rutrum massa, quis suscipit purus lacus eget est. Sed nisi nulla, tempus id dictum a, cursus ut felis. Aliquam orci magna, rutrum nec tempor ac, fermentum quis eros. Sed ullamcorper felis sit amet tristique sagittis. Nullam sed tempus mi. Morbi sit amet lacinia leo. Nunc facilisis orci id consectetur dignissim. Integer dictum consectetur enim. Vivamus auctor, turpis ut eleifend pharetra, purus magna mattis arcu, vel pharetra tellus orci eget ex. Integer blandit posuere vehicula. Ut ipsum lorem, efficitur vitae eleifend tincidunt, fermentum nec lacus. Ut nec fermentum dui.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi scelerisque dolor lacus, ut vehicula arcu dapibus id. Morbi iaculis fermentum blandit. Curabitur tempus, ante et vehicula tempor, urna velit rutrum massa, quis suscipit purus lacus eget est. Sed nisi nulla, tempus id dictum a, cursus ut felis. Aliquam orci magna, rutrum nec tempor ac, fermentum quis eros. Sed ullamcorper felis sit amet tristique sagittis. Nullam sed tempus mi. Morbi sit amet lacinia leo. Nunc facilisis orci id consectetur dignissim. Integer dictum consectetur enim. Vivamus auctor, turpis ut eleifend pharetra, purus magna mattis arcu, vel pharetra tellus orci eget ex. Integer blandit posuere vehicula. Ut ipsum lorem, efficitur vitae eleifend tincidunt, fermentum nec lacus. Ut nec fermentum dui.
</p>
<b>It worked!</b>
</ion-content>
65 changes: 56 additions & 9 deletions src/components/infinite-scroll/infinite-scroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { DomController } from '../../platform/dom-controller';
* @name InfiniteScroll
* @description
* The Infinite Scroll allows you to perform an action when the user
* scrolls a specified distance from the bottom of the page.
* scrolls a specified distance from the bottom or top of the page.
*
* The expression assigned to the `infinite` event is called when
* the user scrolls to the specified distance. When this expression
Expand Down Expand Up @@ -148,6 +148,7 @@ export class InfiniteScroll {
_thr: string = '15%';
_thrPx: number = 0;
_thrPc: number = 0.15;
_position: string = POSITION_BOTTOM;
_init: boolean = false;


Expand Down Expand Up @@ -192,6 +193,23 @@ export class InfiniteScroll {
this.enable(shouldEnable);
}

/**
* @input {string} The position of the infinite scroll element.
* The value can be either `top` or `bottom`.
* Default is `bottom`.
*/
@Input()
get position(): string {
return this._position;
}
set position(val: string) {
if (val === POSITION_TOP || val === POSITION_BOTTOM) {
this._position = val;
} else {
console.error(`Invalid value for ion-infinite-scroll's position input. Its value should be '${POSITION_BOTTOM}' or '${POSITION_TOP}'.`);
}
}

/**
* @output {event} Emitted when the scroll reaches
* the threshold distance. From within your infinite handler,
Expand Down Expand Up @@ -229,17 +247,20 @@ export class InfiniteScroll {

// ******** DOM READ ****************
const d = this._content.getContentDimensions();
const height = d.contentHeight;

let reloadY = d.contentHeight;
if (this._thrPc) {
reloadY += (reloadY * this._thrPc);
} else {
reloadY += this._thrPx;
}
const threshold = this._thrPc ? (height * this._thrPc) : this._thrPx;

// ******** DOM READS ABOVE / DOM WRITES BELOW ****************

const distanceFromInfinite = ((d.scrollHeight - infiniteHeight) - d.scrollTop) - reloadY;
let distanceFromInfinite: number;

if (this._position === POSITION_BOTTOM) {
distanceFromInfinite = ((d.scrollHeight - infiniteHeight) - d.scrollTop) - height - threshold;
} else if (this._position === POSITION_TOP) {
distanceFromInfinite = d.scrollTop - infiniteHeight - threshold;
}

if (distanceFromInfinite < 0) {
// ******** DOM WRITE ****************
this._dom.write(() => {
Expand Down Expand Up @@ -267,7 +288,26 @@ export class InfiniteScroll {
* to `enabled`.
*/
complete() {
if (this.state === STATE_LOADING) {
if (this._position === POSITION_TOP) {
// ******** DOM READ ****************
// Save the current content dimensions before the UI updates
const prevDim = this._content.getContentDimensions();

// ******** DOM READ ****************
this._dom.read(() => {
// UI has updated, save the new content dimensions
const newDim = this._content.getContentDimensions();

// New content was added on top, so the scroll position should be changed immediately to prevent it from jumping around
const newScrollTop = newDim.scrollHeight - (prevDim.scrollHeight - prevDim.scrollTop);

// ******** DOM WRITE ****************
this._dom.write(() => {
this._content.scrollTop = newScrollTop;
this.state = STATE_ENABLED;
});
});
} else {
this.state = STATE_ENABLED;
}
}
Expand Down Expand Up @@ -319,6 +359,10 @@ export class InfiniteScroll {
ngAfterContentInit() {
this._init = true;
this._setListeners(this.state !== STATE_DISABLED);

if (this._position === POSITION_TOP) {
this._content.scrollDownOnLoad = true;
}
}

/**
Expand All @@ -333,3 +377,6 @@ export class InfiniteScroll {
const STATE_ENABLED = 'enabled';
const STATE_DISABLED = 'disabled';
const STATE_LOADING = 'loading';

const POSITION_TOP = 'top';
const POSITION_BOTTOM = 'bottom';
23 changes: 23 additions & 0 deletions src/components/infinite-scroll/test/infinite-scroll.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,29 @@ describe('Infinite Scroll', () => {

});

describe('position', () => {

it('should default to bottom', () => {
expect(inf._position).toEqual('bottom');
});

it('should set to top', () => {
inf.position = 'top';
expect(inf._position).toEqual('top');
});

it('should set to bottom', () => {
inf.position = 'bottom';
expect(inf._position).toEqual('bottom');
});

it('should not set to anything else', () => {
inf.position = 'derp';
expect(inf._position).toEqual('bottom');
});

});


let config = mockConfig();
let inf: InfiniteScroll;
Expand Down
95 changes: 95 additions & 0 deletions src/components/infinite-scroll/test/position-top/app.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Component, ViewChild, NgModule } from '@angular/core';
import { Content, IonicApp, IonicModule, InfiniteScroll, NavController } from '../../../../../ionic-angular';


@Component({
templateUrl: 'main.html'
})
export class E2EPage1 {
@ViewChild(InfiniteScroll) infiniteScroll: InfiniteScroll;
@ViewChild(Content) content: Content;
items: number[] = [];
enabled: boolean = true;

constructor(public navCtrl: NavController) {
for (var i = 0; i < 30; i++) {
this.items.unshift( this.items.length );
}
}

doInfinite(infiniteScroll: InfiniteScroll) {
console.log('Begin async operation');

getAsyncData().then(newData => {
for (var i = 0; i < newData.length; i++) {
this.items.unshift( this.items.length );
}

console.log('Finished receiving data, async operation complete');
infiniteScroll.complete();

if (this.items.length > 90) {
this.enabled = false;
}
});
}

goToPage2() {
this.navCtrl.push(E2EPage2);
}

toggleInfiniteScroll() {
this.enabled = !this.enabled;
}
}


@Component({
template: '<ion-content><button ion-button (click)="navCtrl.pop()">Pop</button></ion-content>'
})
export class E2EPage2 {
constructor(public navCtrl: NavController) {}
}


@Component({
template: '<ion-nav [root]="root"></ion-nav>'
})
export class E2EApp {
root = E2EPage1;
}

@NgModule({
declarations: [
E2EApp,
E2EPage1,
E2EPage2
],
imports: [
IonicModule.forRoot(E2EApp)
],
bootstrap: [IonicApp],
entryComponents: [
E2EApp,
E2EPage1,
E2EPage2
]
})
export class AppModule {}


function getAsyncData(): Promise<any[]> {
// async return mock data
return new Promise(resolve => {

setTimeout(() => {
let data: number[] = [];
for (var i = 0; i < 30; i++) {
data.unshift(i);
}

resolve(data);
}, 2000);

});
}
31 changes: 31 additions & 0 deletions src/components/infinite-scroll/test/position-top/main.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<ion-header>

<ion-toolbar>
<ion-title>Infinite Scroll</ion-title>
</ion-toolbar>

</ion-header>


<ion-content>

<ion-infinite-scroll (ionInfinite)="doInfinite($event)" position="top" [enabled]="enabled">
<ion-infinite-scroll-content>
</ion-infinite-scroll-content>
</ion-infinite-scroll>

<ion-list>
<button ion-item (click)="goToPage2()" *ngFor="let item of items">
{{ item }}
</button>
</ion-list>

<p>
InfiniteScroll is enabled: {{enabled}}
</p>

<button ion-button (click)="toggleInfiniteScroll()" block>
Toggle InfiniteScroll Enabled
</button>

</ion-content>

0 comments on commit 6918275

Please sign in to comment.