Skip to content

Commit 7679ac0

Browse files
committed
feat(virtualScroll): init virtual scroll
Closes #5418
1 parent 0701338 commit 7679ac0

File tree

17 files changed

+2338
-1
lines changed

17 files changed

+2338
-1
lines changed

ionic/components.core.scss

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,16 @@
1616
@import
1717
"components/grid/grid",
1818
"components/icon/icon",
19+
"components/img/img",
1920
"components/infinite-scroll/infinite-scroll",
2021
"components/loading/loading",
2122
"components/menu/menu",
2223
"components/modal/modal",
2324
"components/refresher/refresher",
2425
"components/scroll/scroll",
2526
"components/slides/slides",
26-
"components/spinner/spinner";
27+
"components/spinner/spinner",
28+
"components/virtual-scroll/virtual-scroll";
2729

2830

2931
// Ionicons (to be replaced with SVGs)

ionic/components.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,4 @@ export * from './components/tabs/tab'
4545
export * from './components/tap-click/tap-click'
4646
export * from './components/toggle/toggle'
4747
export * from './components/toolbar/toolbar'
48+
export * from './components/virtual-scroll/virtual-scroll'
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import {ViewChild, ElementRef} from 'angular2/core';
2+
import {App, Page} from 'ionic-angular';
3+
4+
5+
@Page({
6+
templateUrl: 'main.html'
7+
})
8+
class E2EPage {
9+
items = [];
10+
11+
@ViewChild('content') content: ElementRef;
12+
13+
constructor() {
14+
for (var i = 0; i < 14; i++) {
15+
this.items.push(i);
16+
}
17+
18+
}
19+
20+
headerFn(record: any, index: number, records: any[]) {
21+
if (index % 4 === 0) {
22+
return index + ' is divisible by 4';
23+
}
24+
25+
return null;
26+
}
27+
28+
reload() {
29+
window.location.reload(true);
30+
}
31+
32+
}
33+
34+
35+
@App({
36+
template: '<ion-nav [root]="root"></ion-nav>'
37+
})
38+
class E2EApp {
39+
root;
40+
constructor() {
41+
this.root = E2EPage;
42+
}
43+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<ion-navbar *navbar>
2+
<ion-title>Virtual Scroll</ion-title>
3+
<ion-buttons end>
4+
<button (click)="reload()">
5+
Reload
6+
</button>
7+
</ion-buttons>
8+
</ion-navbar>
9+
10+
<ion-content>
11+
12+
13+
<ion-list [virtualScroll]="items"
14+
[headerFn]="headerFn">
15+
16+
<ion-item-divider *virtualHeader="#header">
17+
Header: {{header}}
18+
</ion-item-divider>
19+
20+
<ion-item *virtualItem="#item">
21+
Item: {{item}}
22+
</ion-item>
23+
24+
</ion-list>
25+
26+
27+
</ion-content>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import {ViewEncapsulation} from 'angular2/core';
2+
import {App, Page} from 'ionic-angular';
3+
4+
5+
@Page({
6+
templateUrl: 'main.html',
7+
encapsulation: ViewEncapsulation.None
8+
})
9+
class E2EPage {
10+
items = [];
11+
12+
constructor() {
13+
for (var i = 0; i < 500; i++) {
14+
this.items.push({
15+
imgSrc: `../../img/img/${images[rotateImg]}.jpg?${Math.random()}`,
16+
imgHeight: Math.floor((Math.random() * 50) + 150),
17+
name: i + ' - ' + images[rotateImg],
18+
content: lorem.substring(0, (Math.random() * (lorem.length - 100)) + 100)
19+
});
20+
21+
rotateImg++;
22+
if (rotateImg === images.length) rotateImg = 0;
23+
}
24+
}
25+
26+
}
27+
28+
29+
@App({
30+
template: '<ion-nav [root]="root"></ion-nav>',
31+
})
32+
class E2EApp {
33+
root;
34+
constructor() {
35+
this.root = E2EPage;
36+
}
37+
}
38+
39+
const lorem = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';
40+
41+
const images = [
42+
'bandit',
43+
'batmobile',
44+
'blues-brothers',
45+
'bueller',
46+
'delorean',
47+
'eleanor',
48+
'general-lee',
49+
'ghostbusters',
50+
'knight-rider',
51+
'mirth-mobile',
52+
];
53+
54+
let rotateImg = 0;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<ion-toolbar><ion-title>Virtual Scroll: Cards</ion-title></ion-toolbar>
2+
3+
<ion-content>
4+
5+
<div [virtualScroll]="items" approxItemHeight="320px">
6+
7+
<ion-card *virtualItem="#item">
8+
9+
<div>
10+
<ion-img [src]="item.imgSrc" [height]="item.imgHeight"></ion-img>
11+
</div>
12+
13+
<ion-item>
14+
<ion-avatar item-left>
15+
<ion-img [src]="item.imgSrc"></ion-img>
16+
</ion-avatar>
17+
<h2>{{ item.name }}</h2>
18+
</ion-item>
19+
20+
<ion-card-content>
21+
{{ item.content }}
22+
</ion-card-content>
23+
24+
</ion-card>
25+
26+
</div>
27+
28+
</ion-content>
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import {ViewEncapsulation} from 'angular2/core';
2+
import {App, Page} from 'ionic-angular';
3+
4+
5+
@Page({
6+
templateUrl: 'main.html',
7+
encapsulation: ViewEncapsulation.None
8+
})
9+
class E2EPage {
10+
lastMonth: number;
11+
items = [];
12+
13+
constructor() {
14+
var utcSeconds = 787523438; // Dec 15, 1994
15+
var d = new Date(0);
16+
d.setUTCSeconds(utcSeconds);
17+
18+
for (var i = 0; i < 1000; i++) {
19+
this.items.push({
20+
index: i,
21+
date: d,
22+
imgSrc: `../../img/img/${images[rotateImg]}.jpg?${Math.random()}`,
23+
});
24+
25+
rotateImg++;
26+
if (rotateImg === images.length) rotateImg = 0;
27+
28+
if (i < 100) {
29+
utcSeconds += 237600; // 2.75 days
30+
} else {
31+
utcSeconds += (Math.random() * 237600) + 86400;
32+
}
33+
34+
d = new Date(0);
35+
d.setUTCSeconds(utcSeconds);
36+
}
37+
}
38+
39+
headerFn(record: any, recordIndex: number, records: any[]) {
40+
if (this.lastMonth !== record.date.getMonth()) {
41+
this.lastMonth = record.date.getMonth();
42+
43+
return {
44+
date: monthNames[this.lastMonth] + ' ' + record.date.getFullYear()
45+
}
46+
}
47+
48+
return null;
49+
}
50+
51+
footerFn(record: any, recordIndex: number, records: any[]) {
52+
53+
if (recordIndex === records.length - 1) {
54+
return true;
55+
56+
} else {
57+
if (records[recordIndex + 1].date.getMonth() !== this.lastMonth) {
58+
return true;
59+
}
60+
}
61+
62+
return null;
63+
}
64+
65+
ngDoCheck() {
66+
console.log('DoCheck')
67+
}
68+
69+
reload() {
70+
window.location.reload(true);
71+
}
72+
73+
}
74+
75+
76+
@App({
77+
template: '<ion-nav [root]="root"></ion-nav>',
78+
})
79+
class E2EApp {
80+
root;
81+
constructor() {
82+
this.root = E2EPage;
83+
}
84+
}
85+
86+
var monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
87+
88+
const images = [
89+
'bandit',
90+
'batmobile',
91+
'blues-brothers',
92+
'bueller',
93+
'delorean',
94+
'eleanor',
95+
'general-lee',
96+
'ghostbusters',
97+
'knight-rider',
98+
'mirth-mobile',
99+
];
100+
101+
let rotateImg = 0;
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<style>
2+
.virtual-header {
3+
width: 100%;
4+
margin-bottom: 5px;
5+
padding: 10px;
6+
background: #eee;
7+
}
8+
.virtual-item {
9+
display: inline-block;
10+
vertical-align: top;
11+
width: 80px;
12+
height: 80px;
13+
margin-left: 5px;
14+
margin-bottom: 5px;
15+
border: 1px solid gray;
16+
}
17+
.virtual-footer {
18+
display: inline-block;
19+
width: 80px;
20+
height: 80px;
21+
border: 1px solid red;
22+
margin-left: 5px;
23+
margin-bottom: 5px;
24+
padding: 10px;
25+
}
26+
.virtual-scroll > :first-child {
27+
border-top: 2px solid blue;
28+
}
29+
.virtual-scroll > :last-child {
30+
background: red;
31+
}
32+
</style>
33+
34+
<ion-navbar *navbar>
35+
<ion-title>Virtual Scroll: Image Gallery</ion-title>
36+
<ion-buttons end>
37+
<button (click)="reload()">
38+
Reload
39+
</button>
40+
</ion-buttons>
41+
</ion-navbar>
42+
43+
<ion-content>
44+
45+
<ion-list [virtualScroll]="items"
46+
[headerFn]="headerFn"
47+
[footerFn]="footerFn"
48+
approxItemWidth="80px"
49+
approxItemHeight="80px"
50+
approxFooterWidth="80px"
51+
approxFooterHeight="80px"
52+
approxHeaderWidth="100%"
53+
approxHeaderHeight="36px">
54+
55+
<div *virtualHeader="#header" class="virtual-header">
56+
Header: {{header.date}}
57+
</div>
58+
59+
<div *virtualItem="#item" class="virtual-item">
60+
<ion-img [src]="item.imgSrc"></ion-img>
61+
<!--{{ item.index }}-->
62+
</div>
63+
64+
<div *virtualFooter="#footer" class="virtual-footer">
65+
footer
66+
</div>
67+
68+
</ion-list>
69+
70+
</ion-content>
71+
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import {App, Page} from 'ionic-angular';
2+
3+
4+
@Page({
5+
templateUrl: 'main.html'
6+
})
7+
class E2EPage {
8+
items = [];
9+
10+
constructor() {
11+
12+
for (var i = 0; i < 5000; i++) {
13+
14+
this.items.push({
15+
isHeader: ((i % 10) === 0),
16+
fontSize: Math.floor((Math.random() * 32) + 16) + 'px',
17+
item: i
18+
});
19+
20+
}
21+
}
22+
23+
headerFn(record, recordIndex) {
24+
if (recordIndex > 0 && recordIndex % 100 === 0) {
25+
return recordIndex;
26+
}
27+
return null;
28+
}
29+
30+
}
31+
32+
33+
@App({
34+
template: '<ion-nav [root]="root"></ion-nav>',
35+
})
36+
class E2EApp {
37+
root;
38+
constructor() {
39+
this.root = E2EPage;
40+
}
41+
}

0 commit comments

Comments
 (0)