diff --git a/js/angular/directive/collectionRepeat.js b/js/angular/directive/collectionRepeat.js
index 2454b1f42d..7175ea473b 100644
--- a/js/angular/directive/collectionRepeat.js
+++ b/js/angular/directive/collectionRepeat.js
@@ -22,13 +22,10 @@
* Pixel amounts or percentages are allowed (see below).
* 3. The elements rendered will be absolutely positioned: be sure to let your CSS work with
* this (see below).
- * 4. Keep the HTML of your repeated elements as simple as possible.
- * The more complicated your elements, the more likely it is that the on-demand compilation will cause
- * some jerkiness in the user's scrolling.
- * 6. Each collection-repeat list will take up all of its parent scrollView's space.
+ * 4. Each collection-repeat list will take up all of its parent scrollView's space.
* If you wish to have multiple lists on one page, put each list within its own
* {@link ionic.directive:ionScroll ionScroll} container.
- * 7. You should not use the ng-show and ng-hide directives on your ion-content/ion-scroll elements that
+ * 5. You should not use the ng-show and ng-hide directives on your ion-content/ion-scroll elements that
* have a collection-repeat inside. ng-show and ng-hide apply the `display: none` css rule to the content's
* style, causing the scrollView to read the width and height of the content as 0. Resultingly,
* collection-repeat will render elements that have just been un-hidden incorrectly.
@@ -154,6 +151,10 @@ function($collectionRepeatManager, $collectionDataSource, $parse) {
require: '^$ionicScroll',
controller: [function(){}],
link: function($scope, $element, $attr, scrollCtrl, $transclude) {
+ var wrap = jqLite('
');
+ $element.parent()[0].insertBefore(wrap[0], $element[0]);
+ wrap.append($element);
+
var scrollView = scrollCtrl.scrollView;
if (scrollView.options.scrollingX && scrollView.options.scrollingY) {
throw new Error(COLLECTION_REPEAT_SCROLLVIEW_XY_ERROR);
@@ -216,9 +217,32 @@ function($collectionRepeatManager, $collectionDataSource, $parse) {
rerender(value);
});
+ var scrollViewContent = scrollCtrl.scrollView.__content;
function rerender(value) {
+ var beforeSiblings = [];
+ var afterSiblings = [];
+ var before = true;
+ forEach(scrollViewContent.children, function(node, i) {
+ if ( ionic.DomUtil.elementIsDescendant($element[0], node, scrollViewContent) ) {
+ before = false;
+ } else {
+ var width = node.offsetWidth;
+ var height = node.offsetHeight;
+ if (width && height) {
+ var element = jqLite(node);
+ (before ? beforeSiblings : afterSiblings).push({
+ width: node.offsetWidth,
+ height: node.offsetHeight,
+ element: element,
+ scope: element.isolateScope() || element.scope(),
+ isOutside: true
+ });
+ }
+ }
+ });
+
scrollView.resize();
- dataSource.setData(value);
+ dataSource.setData(value, beforeSiblings, afterSiblings);
collectionRepeatManager.resize();
}
function onWindowResize() {
@@ -237,7 +261,7 @@ function($collectionRepeatManager, $collectionDataSource, $parse) {
}]);
// Fix for #1674
-// Problem: if an ngSrc or ngHref expression evaluates to a falsy value, it will
+// Problem: if an ngSrc or ngHref expression evaluates to a falsy value, it will
// not erase the previous truthy value of the href.
// In collectionRepeat, we re-use elements from before. So if the ngHref expression
// evaluates to truthy for item 1 and then falsy for item 2, if an element changes
@@ -248,13 +272,13 @@ function collectionRepeatSrcDirective(ngAttrName, attrName) {
return [function() {
return {
priority: '99', // it needs to run after the attributes are interpolated
- require: '^?collectionRepeat',
link: function(scope, element, attr, collectionRepeatCtrl) {
if (!collectionRepeatCtrl) return;
attr.$observe(ngAttrName, function(value) {
- if (!value) {
- element.removeAttr(attrName);
- }
+ element[0][attr] = '';
+ setTimeout(function() {
+ element[0][attr] = value;
+ });
});
}
};
diff --git a/js/angular/directive/infiniteScroll.js b/js/angular/directive/infiniteScroll.js
index 1fd8b285ac..3450507e9e 100644
--- a/js/angular/directive/infiniteScroll.js
+++ b/js/angular/directive/infiniteScroll.js
@@ -60,24 +60,19 @@ IonicModule
.directive('ionInfiniteScroll', ['$timeout', function($timeout) {
function calculateMaxValue(distance, maximum, isPercent) {
return isPercent ?
- maximum * (1 - parseInt(distance,10) / 100) :
- maximum - parseInt(distance, 10);
+ maximum * (1 - parseFloat(distance,10) / 100) :
+ maximum - parseFloat(distance, 10);
}
return {
restrict: 'E',
require: ['^$ionicScroll', 'ionInfiniteScroll'],
- template:
- '
',
+ template: '
',
scope: true,
controller: ['$scope', '$attrs', function($scope, $attrs) {
this.isLoading = false;
this.scrollView = null; //given by link function
this.getMaxScroll = function() {
- var distance = ($attrs.distance || '1%').trim();
+ var distance = ($attrs.distance || '2.5%').trim();
var isPercent = distance.indexOf('%') !== -1;
var maxValues = this.scrollView.getScrollMax();
return {
@@ -109,6 +104,7 @@ IonicModule
$element[0].classList.remove('active');
$timeout(function() {
scrollView.resize();
+ checkBounds();
}, 0, false);
infiniteScrollCtrl.isLoading = false;
};
diff --git a/js/angular/service/collectionRepeatDataSource.js b/js/angular/service/collectionRepeatDataSource.js
index 490df0cebe..11eacbf6af 100644
--- a/js/angular/service/collectionRepeatDataSource.js
+++ b/js/angular/service/collectionRepeatDataSource.js
@@ -4,12 +4,16 @@ IonicModule
'$parse',
'$rootScope',
function($cacheFactory, $parse, $rootScope) {
+ function hideWithTransform(element) {
+ element.css(ionic.CSS.TRANSFORM, 'translate3d(-2000px,-2000px,0)');
+ }
function CollectionRepeatDataSource(options) {
var self = this;
this.scope = options.scope;
this.transcludeFn = options.transcludeFn;
this.transcludeParent = options.transcludeParent;
+ this.element = options.element;
this.keyExpr = options.keyExpr;
this.listExpr = options.listExpr;
@@ -61,6 +65,8 @@ function($cacheFactory, $parse, $rootScope) {
height: this.heightGetter(this.scope, locals)
};
}, this);
+ this.dimensions = this.beforeSiblings.concat(this.dimensions).concat(this.afterSiblings);
+ this.dataStartIndex = this.beforeSiblings.length;
},
createItem: function() {
var item = {};
@@ -87,6 +93,13 @@ function($cacheFactory, $parse, $rootScope) {
},
attachItemAtIndex: function(index) {
var value = this.data[index];
+
+ if (index < this.dataStartIndex) {
+ return this.beforeSiblings[index];
+ } else if (index > this.data.length) {
+ return this.afterSiblings[index - this.data.length - this.dataStartIndex];
+ }
+
var hash = this.itemHashGetter(index, value);
var item = this.getItem(hash);
@@ -118,23 +131,36 @@ function($cacheFactory, $parse, $rootScope) {
detachItem: function(item) {
delete this.attachedItems[item.hash];
+ //If it's an outside item, only hide it. These items aren't part of collection
+ //repeat's list, only sit outside
+ if (item.isOutside) {
+ hideWithTransform(item.element);
+
// If we are at the limit of backup items, just get rid of the this element
- if (this.backupItemsArray.length >= this.BACKUP_ITEMS_LENGTH) {
+ } else if (this.backupItemsArray.length >= this.BACKUP_ITEMS_LENGTH) {
this.destroyItem(item);
// Otherwise, add it to our backup items
} else {
this.backupItemsArray.push(item);
- item.element.css(ionic.CSS.TRANSFORM, 'translate3d(-2000px,-2000px,0)');
+ hideWithTransform(item.element);
//Don't .$destroy(), just stop watchers and events firing
disconnectScope(item.scope);
}
+
},
getLength: function() {
- return this.data && this.data.length || 0;
+ return this.dimensions && this.dimensions.length || 0;
},
- setData: function(value) {
+ setData: function(value, beforeSiblings, afterSiblings) {
this.data = value || [];
+ this.beforeSiblings = beforeSiblings || [];
+ this.afterSiblings = afterSiblings || [];
this.calculateDataDimensions();
+
+ this.afterSiblings.forEach(function(item) {
+ item.element.css({position: 'absolute', top: '0', left: '0' });
+ hideWithTransform(item.element);
+ });
},
};
diff --git a/js/angular/service/collectionRepeatManager.js b/js/angular/service/collectionRepeatManager.js
index 5dbc4da0c3..9ddfa5473b 100644
--- a/js/angular/service/collectionRepeatManager.js
+++ b/js/angular/service/collectionRepeatManager.js
@@ -100,7 +100,24 @@ function($rootScope, $timeout) {
var secondaryScrollSize = this.secondaryScrollSize();
var previousItem;
- return this.dataSource.dimensions.map(function(dim) {
+ this.dataSource.beforeSiblings && this.dataSource.beforeSiblings.forEach(calculateSize, this);
+ var beforeSize = primaryPos + (previousItem ? previousItem.primarySize : 0);
+
+ primaryPos = secondaryPos = 0;
+ previousItem = null;
+
+
+ var dimensions = this.dataSource.dimensions.map(calculateSize, this);
+ var totalSize = primaryPos + (previousItem ? previousItem.primarySize : 0);
+
+ return {
+ beforeSize: beforeSize,
+ totalSize: totalSize,
+ dimensions: dimensions
+ };
+
+ function calculateSize(dim) {
+
//Each dimension is an object {width: Number, height: Number} provided by
//the dataSource
var rect = {
@@ -129,12 +146,13 @@ function($rootScope, $timeout) {
previousItem = rect;
return rect;
- }, this);
+ }
},
resize: function() {
- this.dimensions = this.calculateDimensions();
- var lastItem = this.dimensions[this.dimensions.length - 1];
- this.viewportSize = lastItem ? lastItem.primaryPos + lastItem.primarySize : 0;
+ var result = this.calculateDimensions();
+ this.dimensions = result.dimensions;
+ this.viewportSize = result.totalSize;
+ this.beforeSize = result.beforeSize;
this.setCurrentIndex(0);
this.render(true);
if (!this.dataSource.backupItemsArray.length) {
@@ -219,6 +237,7 @@ function($rootScope, $timeout) {
* the data source to render the correct items into the DOM.
*/
render: function(shouldRedrawAll) {
+ var self = this;
var i;
var isOutOfBounds = ( this.currentIndex >= this.dataSource.getLength() );
// We want to remove all the items and redraw everything if we're out of bounds
@@ -258,10 +277,12 @@ function($rootScope, $timeout) {
// Keep rendering items, adding them until we are past the end of the visible scroll area
i = renderStartIndex;
while ((rect = this.dimensions[i]) && (rect.primaryPos - rect.primarySize < scrollSizeEnd)) {
- this.renderItem(i, rect.primaryPos, rect.secondaryPos);
- i++;
+ doRender(i++);
}
- var renderEndIndex = i - 1;
+ //Add two more items at the end
+ doRender(i++);
+ doRender(i);
+ var renderEndIndex = i;
// Remove any items that were rendered and aren't visible anymore
for (i in this.renderedItems) {
@@ -271,6 +292,17 @@ function($rootScope, $timeout) {
}
this.setCurrentIndex(startIndex);
+
+ function doRender(dataIndex) {
+ var rect = self.dimensions[dataIndex];
+ if (!rect) {
+
+ }else if (dataIndex < self.dataSource.dataStartIndex) {
+ // do nothing
+ } else {
+ self.renderItem(dataIndex, rect.primaryPos - self.beforeSize, rect.secondaryPos);
+ }
+ }
},
renderItem: function(dataIndex, primaryPos, secondaryPos) {
// Attach an item, and set its transform position to the required value
@@ -302,6 +334,15 @@ function($rootScope, $timeout) {
}
};
+ var exceptions = {'renderScroll':1, 'renderIfNeeded':1};
+ forEach(CollectionRepeatManager.prototype, function(method, key) {
+ if (exceptions[key]) return;
+ CollectionRepeatManager.prototype[key] = function() {
+ console.log(key + '(', arguments, ')');
+ return method.apply(this, arguments);
+ };
+ });
+
return CollectionRepeatManager;
}]);
diff --git a/js/utils/dom.js b/js/utils/dom.js
index 4416ff70ab..f51d2bdadd 100644
--- a/js/utils/dom.js
+++ b/js/utils/dom.js
@@ -210,6 +210,15 @@
});
},
+ elementIsDescendant: function(el, parent, stopAt) {
+ var current = el;
+ do {
+ if (current === parent) return true;
+ current = current.parentNode;
+ } while (current && current !== stopAt);
+ return false;
+ },
+
/**
* @ngdoc method
* @name ionic.DomUtil#getParentWithClass
diff --git a/scss/_scaffolding.scss b/scss/_scaffolding.scss
index 7b6e57145e..90d4039169 100644
--- a/scss/_scaffolding.scss
+++ b/scss/_scaffolding.scss
@@ -245,36 +245,27 @@ body.grade-c {
}
}
-.scroll-refresher-content {
- position: absolute;
- bottom: 15px;
- left: 0;
- width: 100%;
- color: $scroll-refresh-icon-color;
- text-align: center;
-
- font-size: 30px;
-}
-
-// Infinite scroll
-ion-infinite-scroll .scroll-infinite {
- position: relative;
- overflow: hidden;
- margin-top: -70px;
+ion-infinite-scroll {
height: 60px;
-}
-
-.scroll-infinite-content {
- position: absolute;
- bottom: -1px;
- left: 0;
width: 100%;
- color: #666666;
- text-align: center;
- font-size: 30px; }
+ opacity: 0;
+ display: block;
-ion-infinite-scroll.active .scroll-infinite {
- margin-top: -30px;
+ @include transition(opacity 0.25s);
+ @include display-flex();
+ @include flex-direction(row);
+ @include justify-content(center);
+ @include align-items(center);
+
+ .icon {
+ color: #666666;
+ font-size: 30px;
+ color: $scroll-refresh-icon-color;
+ }
+
+ &.active {
+ opacity: 1;
+ }
}
.overflow-scroll {
diff --git a/test/html/infinite-scroll.html b/test/html/infinite-scroll.html
index 3be794b342..c060c12241 100644
--- a/test/html/infinite-scroll.html
+++ b/test/html/infinite-scroll.html
@@ -26,12 +26,11 @@
+