Skip to content

Commit f0a1c03

Browse files
committed
feat(collection-repeat): add repeat lsdirective for huge lists
1 parent 73b9775 commit f0a1c03

File tree

5 files changed

+556
-42
lines changed

5 files changed

+556
-42
lines changed

Diff for: demos/collection-repeat/index.html

+5-1
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ <h1 class="title">3000 Contacts</h1>
3535
<a class="item"
3636
collection-repeat="item in getContacts()"
3737
collection-item-height="getItemHeight(item)"
38+
collection-item-width="getItemWidth(item)"
3839
ng-href="https://www.google.com/#q={{item.first_name + '+' + item.last_name}}"
39-
ng-style="{height: getItemHeight(item), 'line-height': getItemHeight(item) + 'px', 'padding-top': 0, 'padding-bottom': 0}"
40+
ng-style="{height: getItemHeight(item), 'line-height': getItemHeight(item) + 'px', 'padding-top': 0, 'padding-bottom': 0, width: getItemWidth(item)}"
4041
ng-class="{'item-divider': item.isLetter}">
4142
<img ng-if="!item.isLetter" ng-src="http://placekitten.com/60/{{55 + ($index % 10)}}">
4243
{{item.letter || (item.first_name+' '+item.last_name)}}
@@ -83,6 +84,9 @@ <h1 class="title">3000 Contacts</h1>
8384
margin-top: 10px;
8485
margin-right: 10px;
8586
}
87+
.list {
88+
height: 100%;
89+
}
8690
</style>
8791
</body>
8892
</html>

Diff for: demos/collection-repeat/script.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ angular.module('contactsApp', ['ionic'])
3939

4040
//Letters are shorter, everything else is 52 pixels
4141
$scope.getItemHeight = function(item) {
42-
return item.isLetter ? 38 : 80;
42+
return item.isLetter ? 45 : '25%';
43+
};
44+
$scope.getItemWidth = function(item) {
45+
return item.isLetter ? '100%' : '50%';
4346
};
4447

4548
var letterHasMatch = {};

Diff for: js/angular/directive/collectionRepeat.js

+20-6
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,30 @@ function($collectionRepeatManager, $collectionDataSource, $parse) {
2222
} else if (!isVertical && !$attr.collectionItemWidth) {
2323
throw new Error("collection-repeat expected attribute collection-item-width to be a an expression that returns a number.");
2424
}
25-
var heightGetter = $attr.collectionItemHeight ?
25+
$attr.collectionItemHeight = $attr.collectionItemHeight || '100%';
26+
$attr.collectionItemWidth = $attr.collectionItemWidth || '100%';
27+
28+
var heightParsed = $attr.collectionItemHeight ?
2629
$parse($attr.collectionItemHeight) :
2730
function() { return scrollView.__clientHeight; };
28-
var widthGetter = $attr.collectionItemWidth ?
31+
var widthParsed = $attr.collectionItemWidth ?
2932
$parse($attr.collectionItemWidth) :
3033
function() { return scrollView.__clientWidth; };
31-
console.log(widthGetter());
32-
setTimeout(function() {
33-
console.log(widthGetter());
34-
});
34+
35+
var heightGetter = function(scope, locals) {
36+
var result = heightParsed(scope, locals);
37+
if (angular.isString(result) && result.indexOf('%') > -1) {
38+
return Math.floor(parseInt(result, 10) / 100 * scrollView.__clientHeight);
39+
}
40+
return result;
41+
};
42+
var widthGetter = function(scope, locals) {
43+
var result = widthParsed(scope, locals);
44+
if (angular.isString(result) && result.indexOf('%') > -1) {
45+
return Math.floor(parseInt(result, 10) / 100 * scrollView.__clientWidth);
46+
}
47+
return result;
48+
};
3549

3650
var match = $attr.collectionRepeat.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
3751
if (!match) {

Diff for: js/angular/service/collectionRepeatManager.js

+54-34
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,13 @@ function($rootScope, $timeout) {
99
this.dataSource = options.dataSource;
1010
this.element = options.element;
1111
this.scrollView = options.scrollView;
12-
this.itemSizePrimary = options.itemSizePrimary;
13-
this.itemSizeSecondary = options.itemSizeSecondary;
1412

1513
this.isVertical = !!this.scrollView.options.scrollingY;
1614
this.renderedItems = {};
1715

1816
this.lastRenderScrollValue = this.bufferTransformOffset = this.hasBufferStartIndex =
1917
this.hasBufferEndIndex = this.bufferItemsLength = 0;
18+
this.setCurrentIndex(0);
2019

2120
this.scrollView.__$callback = this.scrollView.__callback;
2221
this.scrollView.__callback = angular.bind(this, this.renderScroll);
@@ -34,12 +33,18 @@ function($rootScope, $timeout) {
3433
this.scrollSize = function() {
3534
return this.scrollView.__clientHeight;
3635
};
37-
this.getSecondaryScrollSize = function() {
36+
this.secondaryScrollSize = function() {
3837
return this.scrollView.__clientWidth;
3938
};
4039
this.transformString = function(y, x) {
4140
return 'translate3d('+x+'px,'+y+'px,0)';
4241
};
42+
this.primaryDimension = function(dim) {
43+
return dim.height;
44+
};
45+
this.secondaryDimension = function(dim) {
46+
return dim.width;
47+
};
4348
} else {
4449
this.scrollView.options.getContentWidth = getViewportSize;
4550

@@ -52,12 +57,18 @@ function($rootScope, $timeout) {
5257
this.scrollSize = function() {
5358
return this.scrollView.__clientWidth;
5459
};
55-
this.getSecondaryScrollSize = function() {
60+
this.secondaryScrollSize = function() {
5661
return this.scrollView.__clientHeight;
5762
};
5863
this.transformString = function(x, y) {
5964
return 'translate3d('+x+'px,'+y+'px,0)';
6065
};
66+
this.primaryDimension = function(dim) {
67+
return dim.width;
68+
};
69+
this.secondaryDimension = function(dim) {
70+
return dim.height;
71+
};
6172
}
6273
}
6374

@@ -67,36 +78,41 @@ function($rootScope, $timeout) {
6778
this.removeItem(i);
6879
}
6980
},
70-
resize: function() {
81+
calculateDimensions: function() {
7182
var primaryPos = 0;
7283
var secondaryPos = 0;
73-
var itemsPerSpace = 0;
7484
var len = this.dataSource.dimensions.length;
75-
this.dimensions = this.dataSource.dimensions.map(function(dimensions, index) {
85+
var secondaryScrollSize = this.secondaryScrollSize();
86+
var previous;
87+
88+
return this.dataSource.dimensions.map(function(dim) {
7689
var rect = {
77-
primarySize: this.isVertical ? dimensions.height : dimensions.width,
78-
secondarySize: this.isVertical ? dimensions.width : dimensions.height,
79-
primaryPos: primaryPos,
80-
secondaryPos: secondaryPos
90+
primarySize: this.primaryDimension(dim),
91+
secondarySize: Math.min(this.secondaryDimension(dim), secondaryScrollSize)
8192
};
8293

83-
itemsPerSpace++;
84-
secondaryPos += rect.secondarySize;
85-
if (secondaryPos >= this.getSecondaryScrollSize()) {
86-
secondaryPos = 0;
87-
primaryPos += rect.primarySize;
88-
89-
if (!this.itemsPerSpace) {
90-
this.itemsPerSpace = itemsPerSpace;
94+
if (previous) {
95+
secondaryPos += previous.secondarySize;
96+
if (previous.primaryPos === primaryPos &&
97+
secondaryPos + rect.secondarySize > secondaryScrollSize) {
98+
secondaryPos = 0;
99+
primaryPos += previous.primarySize;
100+
} else {
91101
}
92102
}
93103

104+
rect.primaryPos = primaryPos;
105+
rect.secondaryPos = secondaryPos;
106+
107+
previous = rect;
94108
return rect;
95109
}, this);
96-
97-
this.viewportSize = primaryPos;
110+
},
111+
resize: function() {
112+
this.dimensions = this.calculateDimensions();
113+
var last = this.dimensions[this.dimensions.length - 1];
114+
this.viewportSize = last ? last.primaryPos + last.primarySize : 0;
98115
this.setCurrentIndex(0);
99-
this.lastRenderScrollValue = 0;
100116
this.render(true);
101117
},
102118
setCurrentIndex: function(index, height) {
@@ -129,40 +145,45 @@ function($rootScope, $timeout) {
129145
},
130146
getIndexForScrollValue: function(i, scrollValue) {
131147
var rect;
132-
//Scrolling down
148+
//Scrolling up
133149
if (scrollValue <= this.dimensions[i].primaryPos) {
134150
while ( (rect = this.dimensions[i - 1]) && rect.primaryPos > scrollValue) {
135-
i -= this.itemsPerSpace;
151+
i--;
136152
}
137-
//Scrolling up
153+
//Scrolling down
138154
} else {
139155
while ( (rect = this.dimensions[i + 1]) && rect.primaryPos < scrollValue) {
140-
i += this.itemsPerSpace;
156+
i++;
141157
}
142158
}
143159
return i;
144160
},
145161
render: function(shouldRedrawAll) {
146-
if (this.currentIndex >= this.dataSource.getLength()) {
147-
return;
148-
}
149-
150162
var i;
151-
if (shouldRedrawAll) {
163+
if (this.currentIndex >= this.dataSource.getLength() || shouldRedrawAll) {
152164
for (i in this.renderedItems) {
153165
this.removeItem(i);
154166
}
167+
if (this.currentIndex >= this.dataSource.getLength()) return null;
155168
}
169+
170+
var rect;
156171
var scrollValue = this.scrollValue();
157172
var scrollDelta = scrollValue - this.lastRenderScrollValue;
158173
var scrollSize = this.scrollSize();
159174
var scrollSizeEnd = scrollSize + scrollValue;
160175
var startIndex = this.getIndexForScrollValue(this.currentIndex, scrollValue);
161-
var bufferStartIndex = Math.max(0, startIndex - this.itemsPerSpace);
176+
177+
//Make buffer start on previous row
178+
var bufferStartIndex = Math.max(startIndex - 1, 0);
179+
while (bufferStartIndex > 0 &&
180+
(rect = this.dimensions[bufferStartIndex]) &&
181+
rect.primaryPos === this.dimensions[startIndex - 1].primaryPos) {
182+
bufferStartIndex--;
183+
}
162184
var startPos = this.dimensions[bufferStartIndex].primaryPos;
163185

164186
i = bufferStartIndex;
165-
var rect;
166187
while ((rect = this.dimensions[i]) && (rect.primaryPos - rect.primarySize < scrollSizeEnd)) {
167188
this.renderItem(i, rect.primaryPos - startPos, rect.secondaryPos);
168189
i++;
@@ -183,7 +204,6 @@ function($rootScope, $timeout) {
183204
}
184205
},
185206
renderItem: function(dataIndex, primaryPos, secondaryPos) {
186-
var self = this;
187207
var item = this.dataSource.getItem(dataIndex);
188208
if (item) {
189209
this.dataSource.attachItem(item);

0 commit comments

Comments
 (0)