Skip to content

Commit 6918275

Browse files
Manduromanucorporat
authored andcommitted
feat(infinite): add scroll in opposite direction (#8099)
* 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
1 parent f9f9a1b commit 6918275

File tree

7 files changed

+298
-10
lines changed

7 files changed

+298
-10
lines changed

src/components/content/content.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ export class Content extends Ion implements OnDestroy, OnInit {
170170
_viewCtrlReadSub: any;
171171
/** @internal */
172172
_viewCtrlWriteSub: any;
173+
/** @internal */
174+
_scrollDownOnLoad: boolean = false;
173175

174176
private _imgReqBfr: number;
175177
private _imgRndBfr: number;
@@ -478,13 +480,25 @@ export class Content extends Ion implements OnDestroy, OnInit {
478480
*/
479481
@Input()
480482
get fullscreen(): boolean {
481-
return !!this._fullscreen;
483+
return this._fullscreen;
482484
}
483485

484486
set fullscreen(val: boolean) {
485487
this._fullscreen = isTrueProperty(val);
486488
}
487489

490+
/**
491+
* @input {boolean} If true, the content will scroll down on load.
492+
*/
493+
@Input()
494+
get scrollDownOnLoad(): boolean {
495+
return this._scrollDownOnLoad;
496+
}
497+
498+
set scrollDownOnLoad(val: boolean) {
499+
this._scrollDownOnLoad = isTrueProperty(val);
500+
}
501+
488502
/**
489503
* @private
490504
*/
@@ -830,6 +844,12 @@ export class Content extends Ion implements OnDestroy, OnInit {
830844
this._tabs.setTabbarPosition(-1, 0);
831845
}
832846
}
847+
848+
// Scroll the page all the way down after setting dimensions
849+
if (this._scrollDownOnLoad) {
850+
this.scrollToBottom(0);
851+
this._scrollDownOnLoad = false;
852+
}
833853
}
834854

835855
/**
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Component, NgModule } from '@angular/core';
2+
import { IonicApp, IonicModule } from '../../../../../ionic-angular';
3+
4+
5+
@Component({
6+
templateUrl: 'main.html'
7+
})
8+
export class E2EPage {}
9+
10+
11+
@Component({
12+
template: '<ion-nav [root]="root"></ion-nav>'
13+
})
14+
export class E2EApp {
15+
root = E2EPage;
16+
}
17+
18+
@NgModule({
19+
declarations: [
20+
E2EApp,
21+
E2EPage,
22+
],
23+
imports: [
24+
IonicModule.forRoot(E2EApp)
25+
],
26+
bootstrap: [IonicApp],
27+
entryComponents: [
28+
E2EApp,
29+
E2EPage,
30+
]
31+
})
32+
export class AppModule {}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<ion-content scrollDownOnLoad="true">
2+
<b>This page should scroll down on load</b>
3+
<p>
4+
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.
5+
</p>
6+
<p>
7+
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.
8+
</p>
9+
<p>
10+
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.
11+
</p>
12+
<p>
13+
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.
14+
</p>
15+
<p>
16+
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.
17+
</p>
18+
<p>
19+
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.
20+
</p>
21+
<p>
22+
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.
23+
</p>
24+
<p>
25+
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.
26+
</p>
27+
<p>
28+
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.
29+
</p>
30+
<p>
31+
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.
32+
</p>
33+
<p>
34+
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.
35+
</p>
36+
<p>
37+
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.
38+
</p>
39+
<b>It worked!</b>
40+
</ion-content>

src/components/infinite-scroll/infinite-scroll.ts

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { DomController } from '../../platform/dom-controller';
88
* @name InfiniteScroll
99
* @description
1010
* The Infinite Scroll allows you to perform an action when the user
11-
* scrolls a specified distance from the bottom of the page.
11+
* scrolls a specified distance from the bottom or top of the page.
1212
*
1313
* The expression assigned to the `infinite` event is called when
1414
* the user scrolls to the specified distance. When this expression
@@ -148,6 +148,7 @@ export class InfiniteScroll {
148148
_thr: string = '15%';
149149
_thrPx: number = 0;
150150
_thrPc: number = 0.15;
151+
_position: string = POSITION_BOTTOM;
151152
_init: boolean = false;
152153

153154

@@ -192,6 +193,23 @@ export class InfiniteScroll {
192193
this.enable(shouldEnable);
193194
}
194195

196+
/**
197+
* @input {string} The position of the infinite scroll element.
198+
* The value can be either `top` or `bottom`.
199+
* Default is `bottom`.
200+
*/
201+
@Input()
202+
get position(): string {
203+
return this._position;
204+
}
205+
set position(val: string) {
206+
if (val === POSITION_TOP || val === POSITION_BOTTOM) {
207+
this._position = val;
208+
} else {
209+
console.error(`Invalid value for ion-infinite-scroll's position input. Its value should be '${POSITION_BOTTOM}' or '${POSITION_TOP}'.`);
210+
}
211+
}
212+
195213
/**
196214
* @output {event} Emitted when the scroll reaches
197215
* the threshold distance. From within your infinite handler,
@@ -229,17 +247,20 @@ export class InfiniteScroll {
229247

230248
// ******** DOM READ ****************
231249
const d = this._content.getContentDimensions();
250+
const height = d.contentHeight;
232251

233-
let reloadY = d.contentHeight;
234-
if (this._thrPc) {
235-
reloadY += (reloadY * this._thrPc);
236-
} else {
237-
reloadY += this._thrPx;
238-
}
252+
const threshold = this._thrPc ? (height * this._thrPc) : this._thrPx;
239253

240254
// ******** DOM READS ABOVE / DOM WRITES BELOW ****************
241255

242-
const distanceFromInfinite = ((d.scrollHeight - infiniteHeight) - d.scrollTop) - reloadY;
256+
let distanceFromInfinite: number;
257+
258+
if (this._position === POSITION_BOTTOM) {
259+
distanceFromInfinite = ((d.scrollHeight - infiniteHeight) - d.scrollTop) - height - threshold;
260+
} else if (this._position === POSITION_TOP) {
261+
distanceFromInfinite = d.scrollTop - infiniteHeight - threshold;
262+
}
263+
243264
if (distanceFromInfinite < 0) {
244265
// ******** DOM WRITE ****************
245266
this._dom.write(() => {
@@ -267,7 +288,26 @@ export class InfiniteScroll {
267288
* to `enabled`.
268289
*/
269290
complete() {
270-
if (this.state === STATE_LOADING) {
291+
if (this._position === POSITION_TOP) {
292+
// ******** DOM READ ****************
293+
// Save the current content dimensions before the UI updates
294+
const prevDim = this._content.getContentDimensions();
295+
296+
// ******** DOM READ ****************
297+
this._dom.read(() => {
298+
// UI has updated, save the new content dimensions
299+
const newDim = this._content.getContentDimensions();
300+
301+
// New content was added on top, so the scroll position should be changed immediately to prevent it from jumping around
302+
const newScrollTop = newDim.scrollHeight - (prevDim.scrollHeight - prevDim.scrollTop);
303+
304+
// ******** DOM WRITE ****************
305+
this._dom.write(() => {
306+
this._content.scrollTop = newScrollTop;
307+
this.state = STATE_ENABLED;
308+
});
309+
});
310+
} else {
271311
this.state = STATE_ENABLED;
272312
}
273313
}
@@ -319,6 +359,10 @@ export class InfiniteScroll {
319359
ngAfterContentInit() {
320360
this._init = true;
321361
this._setListeners(this.state !== STATE_DISABLED);
362+
363+
if (this._position === POSITION_TOP) {
364+
this._content.scrollDownOnLoad = true;
365+
}
322366
}
323367

324368
/**
@@ -333,3 +377,6 @@ export class InfiniteScroll {
333377
const STATE_ENABLED = 'enabled';
334378
const STATE_DISABLED = 'disabled';
335379
const STATE_LOADING = 'loading';
380+
381+
const POSITION_TOP = 'top';
382+
const POSITION_BOTTOM = 'bottom';

src/components/infinite-scroll/test/infinite-scroll.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,29 @@ describe('Infinite Scroll', () => {
9191

9292
});
9393

94+
describe('position', () => {
95+
96+
it('should default to bottom', () => {
97+
expect(inf._position).toEqual('bottom');
98+
});
99+
100+
it('should set to top', () => {
101+
inf.position = 'top';
102+
expect(inf._position).toEqual('top');
103+
});
104+
105+
it('should set to bottom', () => {
106+
inf.position = 'bottom';
107+
expect(inf._position).toEqual('bottom');
108+
});
109+
110+
it('should not set to anything else', () => {
111+
inf.position = 'derp';
112+
expect(inf._position).toEqual('bottom');
113+
});
114+
115+
});
116+
94117

95118
let config = mockConfig();
96119
let inf: InfiniteScroll;
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { Component, ViewChild, NgModule } from '@angular/core';
2+
import { Content, IonicApp, IonicModule, InfiniteScroll, NavController } from '../../../../../ionic-angular';
3+
4+
5+
@Component({
6+
templateUrl: 'main.html'
7+
})
8+
export class E2EPage1 {
9+
@ViewChild(InfiniteScroll) infiniteScroll: InfiniteScroll;
10+
@ViewChild(Content) content: Content;
11+
items: number[] = [];
12+
enabled: boolean = true;
13+
14+
constructor(public navCtrl: NavController) {
15+
for (var i = 0; i < 30; i++) {
16+
this.items.unshift( this.items.length );
17+
}
18+
}
19+
20+
doInfinite(infiniteScroll: InfiniteScroll) {
21+
console.log('Begin async operation');
22+
23+
getAsyncData().then(newData => {
24+
for (var i = 0; i < newData.length; i++) {
25+
this.items.unshift( this.items.length );
26+
}
27+
28+
console.log('Finished receiving data, async operation complete');
29+
infiniteScroll.complete();
30+
31+
if (this.items.length > 90) {
32+
this.enabled = false;
33+
}
34+
});
35+
}
36+
37+
goToPage2() {
38+
this.navCtrl.push(E2EPage2);
39+
}
40+
41+
toggleInfiniteScroll() {
42+
this.enabled = !this.enabled;
43+
}
44+
}
45+
46+
47+
@Component({
48+
template: '<ion-content><button ion-button (click)="navCtrl.pop()">Pop</button></ion-content>'
49+
})
50+
export class E2EPage2 {
51+
constructor(public navCtrl: NavController) {}
52+
}
53+
54+
55+
@Component({
56+
template: '<ion-nav [root]="root"></ion-nav>'
57+
})
58+
export class E2EApp {
59+
root = E2EPage1;
60+
}
61+
62+
@NgModule({
63+
declarations: [
64+
E2EApp,
65+
E2EPage1,
66+
E2EPage2
67+
],
68+
imports: [
69+
IonicModule.forRoot(E2EApp)
70+
],
71+
bootstrap: [IonicApp],
72+
entryComponents: [
73+
E2EApp,
74+
E2EPage1,
75+
E2EPage2
76+
]
77+
})
78+
export class AppModule {}
79+
80+
81+
function getAsyncData(): Promise<any[]> {
82+
// async return mock data
83+
return new Promise(resolve => {
84+
85+
setTimeout(() => {
86+
let data: number[] = [];
87+
for (var i = 0; i < 30; i++) {
88+
data.unshift(i);
89+
}
90+
91+
resolve(data);
92+
}, 2000);
93+
94+
});
95+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<ion-header>
2+
3+
<ion-toolbar>
4+
<ion-title>Infinite Scroll</ion-title>
5+
</ion-toolbar>
6+
7+
</ion-header>
8+
9+
10+
<ion-content>
11+
12+
<ion-infinite-scroll (ionInfinite)="doInfinite($event)" position="top" [enabled]="enabled">
13+
<ion-infinite-scroll-content>
14+
</ion-infinite-scroll-content>
15+
</ion-infinite-scroll>
16+
17+
<ion-list>
18+
<button ion-item (click)="goToPage2()" *ngFor="let item of items">
19+
{{ item }}
20+
</button>
21+
</ion-list>
22+
23+
<p>
24+
InfiniteScroll is enabled: {{enabled}}
25+
</p>
26+
27+
<button ion-button (click)="toggleInfiniteScroll()" block>
28+
Toggle InfiniteScroll Enabled
29+
</button>
30+
31+
</ion-content>

0 commit comments

Comments
 (0)