Skip to content

Commit 7ddb57e

Browse files
committed
feat(collectionRepeat): other children of ion-content element fit in
Closes #1920. Closes #1866. Closes #1380.
1 parent c0b6426 commit 7ddb57e

12 files changed

+219
-146
lines changed

js/angular/directive/collectionRepeat.js

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,10 @@
2222
* Pixel amounts or percentages are allowed (see below).
2323
* 3. The elements rendered will be absolutely positioned: be sure to let your CSS work with
2424
* this (see below).
25-
* 4. Keep the HTML of your repeated elements as simple as possible.
26-
* The more complicated your elements, the more likely it is that the on-demand compilation will cause
27-
* some jerkiness in the user's scrolling.
28-
* 6. Each collection-repeat list will take up all of its parent scrollView's space.
25+
* 4. Each collection-repeat list will take up all of its parent scrollView's space.
2926
* If you wish to have multiple lists on one page, put each list within its own
3027
* {@link ionic.directive:ionScroll ionScroll} container.
31-
* 7. You should not use the ng-show and ng-hide directives on your ion-content/ion-scroll elements that
28+
* 5. You should not use the ng-show and ng-hide directives on your ion-content/ion-scroll elements that
3229
* have a collection-repeat inside. ng-show and ng-hide apply the `display: none` css rule to the content's
3330
* style, causing the scrollView to read the width and height of the content as 0. Resultingly,
3431
* collection-repeat will render elements that have just been un-hidden incorrectly.
@@ -154,6 +151,10 @@ function($collectionRepeatManager, $collectionDataSource, $parse) {
154151
require: '^$ionicScroll',
155152
controller: [function(){}],
156153
link: function($scope, $element, $attr, scrollCtrl, $transclude) {
154+
var wrap = jqLite('<div style="position:relative;">');
155+
$element.parent()[0].insertBefore(wrap[0], $element[0]);
156+
wrap.append($element);
157+
157158
var scrollView = scrollCtrl.scrollView;
158159
if (scrollView.options.scrollingX && scrollView.options.scrollingY) {
159160
throw new Error(COLLECTION_REPEAT_SCROLLVIEW_XY_ERROR);
@@ -216,9 +217,32 @@ function($collectionRepeatManager, $collectionDataSource, $parse) {
216217
rerender(value);
217218
});
218219

220+
var scrollViewContent = scrollCtrl.scrollView.__content;
219221
function rerender(value) {
222+
var beforeSiblings = [];
223+
var afterSiblings = [];
224+
var before = true;
225+
forEach(scrollViewContent.children, function(node, i) {
226+
if ( ionic.DomUtil.elementIsDescendant($element[0], node, scrollViewContent) ) {
227+
before = false;
228+
} else {
229+
var width = node.offsetWidth;
230+
var height = node.offsetHeight;
231+
if (width && height) {
232+
var element = jqLite(node);
233+
(before ? beforeSiblings : afterSiblings).push({
234+
width: node.offsetWidth,
235+
height: node.offsetHeight,
236+
element: element,
237+
scope: element.isolateScope() || element.scope(),
238+
isOutside: true
239+
});
240+
}
241+
}
242+
});
243+
220244
scrollView.resize();
221-
dataSource.setData(value);
245+
dataSource.setData(value, beforeSiblings, afterSiblings);
222246
collectionRepeatManager.resize();
223247
}
224248
function onWindowResize() {
@@ -237,7 +261,7 @@ function($collectionRepeatManager, $collectionDataSource, $parse) {
237261
}]);
238262

239263
// Fix for #1674
240-
// Problem: if an ngSrc or ngHref expression evaluates to a falsy value, it will
264+
// Problem: if an ngSrc or ngHref expression evaluates to a falsy value, it will
241265
// not erase the previous truthy value of the href.
242266
// In collectionRepeat, we re-use elements from before. So if the ngHref expression
243267
// evaluates to truthy for item 1 and then falsy for item 2, if an element changes
@@ -248,13 +272,13 @@ function collectionRepeatSrcDirective(ngAttrName, attrName) {
248272
return [function() {
249273
return {
250274
priority: '99', // it needs to run after the attributes are interpolated
251-
require: '^?collectionRepeat',
252275
link: function(scope, element, attr, collectionRepeatCtrl) {
253276
if (!collectionRepeatCtrl) return;
254277
attr.$observe(ngAttrName, function(value) {
255-
if (!value) {
256-
element.removeAttr(attrName);
257-
}
278+
element[0][attr] = '';
279+
setTimeout(function() {
280+
element[0][attr] = value;
281+
});
258282
});
259283
}
260284
};

js/angular/directive/infiniteScroll.js

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,24 +60,19 @@ IonicModule
6060
.directive('ionInfiniteScroll', ['$timeout', function($timeout) {
6161
function calculateMaxValue(distance, maximum, isPercent) {
6262
return isPercent ?
63-
maximum * (1 - parseInt(distance,10) / 100) :
64-
maximum - parseInt(distance, 10);
63+
maximum * (1 - parseFloat(distance,10) / 100) :
64+
maximum - parseFloat(distance, 10);
6565
}
6666
return {
6767
restrict: 'E',
6868
require: ['^$ionicScroll', 'ionInfiniteScroll'],
69-
template:
70-
'<div class="scroll-infinite">' +
71-
'<div class="scroll-infinite-content">' +
72-
'<i class="icon {{icon()}} icon-refreshing"></i>' +
73-
'</div>' +
74-
'</div>',
69+
template: '<i class="icon {{icon()}} icon-refreshing"></i>',
7570
scope: true,
7671
controller: ['$scope', '$attrs', function($scope, $attrs) {
7772
this.isLoading = false;
7873
this.scrollView = null; //given by link function
7974
this.getMaxScroll = function() {
80-
var distance = ($attrs.distance || '1%').trim();
75+
var distance = ($attrs.distance || '2.5%').trim();
8176
var isPercent = distance.indexOf('%') !== -1;
8277
var maxValues = this.scrollView.getScrollMax();
8378
return {
@@ -109,6 +104,7 @@ IonicModule
109104
$element[0].classList.remove('active');
110105
$timeout(function() {
111106
scrollView.resize();
107+
checkBounds();
112108
}, 0, false);
113109
infiniteScrollCtrl.isLoading = false;
114110
};

js/angular/service/collectionRepeatDataSource.js

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@ IonicModule
44
'$parse',
55
'$rootScope',
66
function($cacheFactory, $parse, $rootScope) {
7+
function hideWithTransform(element) {
8+
element.css(ionic.CSS.TRANSFORM, 'translate3d(-2000px,-2000px,0)');
9+
}
710

811
function CollectionRepeatDataSource(options) {
912
var self = this;
1013
this.scope = options.scope;
1114
this.transcludeFn = options.transcludeFn;
1215
this.transcludeParent = options.transcludeParent;
16+
this.element = options.element;
1317

1418
this.keyExpr = options.keyExpr;
1519
this.listExpr = options.listExpr;
@@ -61,6 +65,8 @@ function($cacheFactory, $parse, $rootScope) {
6165
height: this.heightGetter(this.scope, locals)
6266
};
6367
}, this);
68+
this.dimensions = this.beforeSiblings.concat(this.dimensions).concat(this.afterSiblings);
69+
this.dataStartIndex = this.beforeSiblings.length;
6470
},
6571
createItem: function() {
6672
var item = {};
@@ -87,6 +93,13 @@ function($cacheFactory, $parse, $rootScope) {
8793
},
8894
attachItemAtIndex: function(index) {
8995
var value = this.data[index];
96+
97+
if (index < this.dataStartIndex) {
98+
return this.beforeSiblings[index];
99+
} else if (index > this.data.length) {
100+
return this.afterSiblings[index - this.data.length - this.dataStartIndex];
101+
}
102+
90103
var hash = this.itemHashGetter(index, value);
91104
var item = this.getItem(hash);
92105

@@ -118,23 +131,36 @@ function($cacheFactory, $parse, $rootScope) {
118131
detachItem: function(item) {
119132
delete this.attachedItems[item.hash];
120133

134+
//If it's an outside item, only hide it. These items aren't part of collection
135+
//repeat's list, only sit outside
136+
if (item.isOutside) {
137+
hideWithTransform(item.element);
138+
121139
// If we are at the limit of backup items, just get rid of the this element
122-
if (this.backupItemsArray.length >= this.BACKUP_ITEMS_LENGTH) {
140+
} else if (this.backupItemsArray.length >= this.BACKUP_ITEMS_LENGTH) {
123141
this.destroyItem(item);
124142
// Otherwise, add it to our backup items
125143
} else {
126144
this.backupItemsArray.push(item);
127-
item.element.css(ionic.CSS.TRANSFORM, 'translate3d(-2000px,-2000px,0)');
145+
hideWithTransform(item.element);
128146
//Don't .$destroy(), just stop watchers and events firing
129147
disconnectScope(item.scope);
130148
}
149+
131150
},
132151
getLength: function() {
133-
return this.data && this.data.length || 0;
152+
return this.dimensions && this.dimensions.length || 0;
134153
},
135-
setData: function(value) {
154+
setData: function(value, beforeSiblings, afterSiblings) {
136155
this.data = value || [];
156+
this.beforeSiblings = beforeSiblings || [];
157+
this.afterSiblings = afterSiblings || [];
137158
this.calculateDataDimensions();
159+
160+
this.afterSiblings.forEach(function(item) {
161+
item.element.css({position: 'absolute', top: '0', left: '0' });
162+
hideWithTransform(item.element);
163+
});
138164
},
139165
};
140166

js/angular/service/collectionRepeatManager.js

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,24 @@ function($rootScope, $timeout) {
100100
var secondaryScrollSize = this.secondaryScrollSize();
101101
var previousItem;
102102

103-
return this.dataSource.dimensions.map(function(dim) {
103+
this.dataSource.beforeSiblings && this.dataSource.beforeSiblings.forEach(calculateSize, this);
104+
var beforeSize = primaryPos + (previousItem ? previousItem.primarySize : 0);
105+
106+
primaryPos = secondaryPos = 0;
107+
previousItem = null;
108+
109+
110+
var dimensions = this.dataSource.dimensions.map(calculateSize, this);
111+
var totalSize = primaryPos + (previousItem ? previousItem.primarySize : 0);
112+
113+
return {
114+
beforeSize: beforeSize,
115+
totalSize: totalSize,
116+
dimensions: dimensions
117+
};
118+
119+
function calculateSize(dim) {
120+
104121
//Each dimension is an object {width: Number, height: Number} provided by
105122
//the dataSource
106123
var rect = {
@@ -129,12 +146,13 @@ function($rootScope, $timeout) {
129146

130147
previousItem = rect;
131148
return rect;
132-
}, this);
149+
}
133150
},
134151
resize: function() {
135-
this.dimensions = this.calculateDimensions();
136-
var lastItem = this.dimensions[this.dimensions.length - 1];
137-
this.viewportSize = lastItem ? lastItem.primaryPos + lastItem.primarySize : 0;
152+
var result = this.calculateDimensions();
153+
this.dimensions = result.dimensions;
154+
this.viewportSize = result.totalSize;
155+
this.beforeSize = result.beforeSize;
138156
this.setCurrentIndex(0);
139157
this.render(true);
140158
if (!this.dataSource.backupItemsArray.length) {
@@ -219,6 +237,7 @@ function($rootScope, $timeout) {
219237
* the data source to render the correct items into the DOM.
220238
*/
221239
render: function(shouldRedrawAll) {
240+
var self = this;
222241
var i;
223242
var isOutOfBounds = ( this.currentIndex >= this.dataSource.getLength() );
224243
// We want to remove all the items and redraw everything if we're out of bounds
@@ -258,10 +277,12 @@ function($rootScope, $timeout) {
258277
// Keep rendering items, adding them until we are past the end of the visible scroll area
259278
i = renderStartIndex;
260279
while ((rect = this.dimensions[i]) && (rect.primaryPos - rect.primarySize < scrollSizeEnd)) {
261-
this.renderItem(i, rect.primaryPos, rect.secondaryPos);
262-
i++;
280+
doRender(i++);
263281
}
264-
var renderEndIndex = i - 1;
282+
//Add two more items at the end
283+
doRender(i++);
284+
doRender(i);
285+
var renderEndIndex = i;
265286

266287
// Remove any items that were rendered and aren't visible anymore
267288
for (i in this.renderedItems) {
@@ -271,6 +292,17 @@ function($rootScope, $timeout) {
271292
}
272293

273294
this.setCurrentIndex(startIndex);
295+
296+
function doRender(dataIndex) {
297+
var rect = self.dimensions[dataIndex];
298+
if (!rect) {
299+
300+
}else if (dataIndex < self.dataSource.dataStartIndex) {
301+
// do nothing
302+
} else {
303+
self.renderItem(dataIndex, rect.primaryPos - self.beforeSize, rect.secondaryPos);
304+
}
305+
}
274306
},
275307
renderItem: function(dataIndex, primaryPos, secondaryPos) {
276308
// Attach an item, and set its transform position to the required value
@@ -302,6 +334,15 @@ function($rootScope, $timeout) {
302334
}
303335
};
304336

337+
var exceptions = {'renderScroll':1, 'renderIfNeeded':1};
338+
forEach(CollectionRepeatManager.prototype, function(method, key) {
339+
if (exceptions[key]) return;
340+
CollectionRepeatManager.prototype[key] = function() {
341+
console.log(key + '(', arguments, ')');
342+
return method.apply(this, arguments);
343+
};
344+
});
345+
305346
return CollectionRepeatManager;
306347
}]);
307348

js/utils/dom.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,15 @@
210210
});
211211
},
212212

213+
elementIsDescendant: function(el, parent, stopAt) {
214+
var current = el;
215+
do {
216+
if (current === parent) return true;
217+
current = current.parentNode;
218+
} while (current && current !== stopAt);
219+
return false;
220+
},
221+
213222
/**
214223
* @ngdoc method
215224
* @name ionic.DomUtil#getParentWithClass

scss/_scaffolding.scss

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -245,36 +245,27 @@ body.grade-c {
245245
}
246246
}
247247

248-
.scroll-refresher-content {
249-
position: absolute;
250-
bottom: 15px;
251-
left: 0;
248+
ion-infinite-scroll {
249+
height: 60px;
252250
width: 100%;
253-
color: $scroll-refresh-icon-color;
254-
text-align: center;
255-
256-
font-size: 30px;
257-
}
251+
opacity: 0;
252+
display: block;
258253

259-
// Infinite scroll
260-
ion-infinite-scroll .scroll-infinite {
261-
position: relative;
262-
overflow: hidden;
263-
margin-top: -70px;
264-
height: 60px;
265-
}
254+
@include transition(opacity 0.25s);
255+
@include display-flex();
256+
@include flex-direction(row);
257+
@include justify-content(center);
258+
@include align-items(center);
266259

267-
.scroll-infinite-content {
268-
position: absolute;
269-
bottom: -1px;
270-
left: 0;
271-
width: 100%;
272-
color: #666666;
273-
text-align: center;
274-
font-size: 30px; }
260+
.icon {
261+
color: #666666;
262+
font-size: 30px;
263+
color: $scroll-refresh-icon-color;
264+
}
275265

276-
ion-infinite-scroll.active .scroll-infinite {
277-
margin-top: -30px;
266+
&.active {
267+
opacity: 1;
268+
}
278269
}
279270

280271
.overflow-scroll {

0 commit comments

Comments
 (0)