fix(collectionRepeat): fix data change while page disconnected, computed dimensions while no data

Closes #3240. Closes #3238.
This commit is contained in:
Andrew
2015-03-07 13:58:53 -07:00
parent b75c78653b
commit 4325025deb
5 changed files with 97 additions and 85 deletions

View File

@@ -83,7 +83,6 @@ function($scope,
$timeout(function() {
scrollView && scrollView.run && scrollView.run();
$element.triggerHandler('scroll.init');
});
self.getScrollView = function() {

View File

@@ -95,7 +95,7 @@ function CollectionRepeatDirective($ionicCollectionManager, $parse, $window, $$r
priority: 1000,
transclude: 'element',
$$tlb: true,
require: '^$ionicScroll',
require: '^^$ionicScroll',
link: postLink
};
@@ -122,6 +122,7 @@ function CollectionRepeatDirective($ionicCollectionManager, $parse, $window, $$r
var heightData = {};
var widthData = {};
var computedStyleDimensions = {};
var data = [];
var repeatManager;
// attr.collectionBufferSize is deprecated
@@ -135,61 +136,33 @@ function CollectionRepeatDirective($ionicCollectionManager, $parse, $window, $$r
// attr.collectionItemWidth is deprecated
var widthExpr = attr.itemWidth || attr.collectionItemWidth;
//Height and width have four 'modes':
//1) Computed Mode
// - Nothing is supplied, so we getComputedStyle() on one element in the list and use
// that width and height value for the width and height of every item. This is re-computed
// every resize.
//2) Constant Mode, Static Integer
// - The user provides a constant number for width or height, in pixels. We parse it,
// store it on the `value` field, and it never changes
//3) Constant Mode, Percent
// - The user provides a percent string for width or height. The getter for percent is
// stored on the `getValue()` field, and is re-evaluated once every resize. The result
// is stored on the `value` field.
//4) Dynamic Mode
// - The user provides a dynamic expression for the width or height. This is re-evaluated
// for every item, stored on the `.getValue()` field.
if (!heightExpr && !widthExpr) {
heightData.computed = widthData.computed = true;
} else {
if (heightExpr) {
parseDimensionAttr(heightExpr, heightData);
} else {
heightData.computed = true;
}
if (!widthExpr) widthExpr = '"100%"';
parseDimensionAttr(widthExpr, widthData);
}
var afterItemsContainer = initAfterItemsContainer();
var afterItemsContainer = angular.element(
scrollView.__content.querySelector('.collection-repeat-after-container')
);
if (!afterItemsContainer.length) {
var elementIsAfterRepeater = false;
var afterNodes = [].filter.call(scrollView.__content.childNodes, function(node) { if (ionic.DomUtil.contains(node, containerNode)) {
elementIsAfterRepeater = true;
return false;
}
return elementIsAfterRepeater;
initDimensions();
var debouncedRefreshDimensions = ionic.animationFrameThrottle(refreshDimensions);
var debouncedOnResize = ionic.animationFrameThrottle(validateResize);
// Dimensions are refreshed on resize or data change.
angular.element($window).on('resize', debouncedOnResize);
$timeout(debouncedRefreshDimensions, 0, false);
scope.$watchCollection(listGetter, function(newValue) {
data = newValue || (newValue = []);
if (!angular.isArray(newValue)) {
throw new Error("collection-repeat expected an array for '" + listExpr + "', " +
"but got a " + typeof value);
}
// Wait for this digest to end before refreshing everything.
scope.$$postDigest(function() {
getRepeatManager().refreshData(newValue);
refreshDimensions();
});
afterItemsContainer = angular.element('<span class="collection-repeat-after-container">');
if (scrollView.options.scrollingX) {
afterItemsContainer.addClass('horizontal');
}
afterItemsContainer.append(afterNodes);
scrollView.__content.appendChild(afterItemsContainer[0]);
}
$$rAF(refreshDimensions);
scrollCtrl.$element.one('scroll.init', refreshDimensions);
var onWindowResize = ionic.animationFrameThrottle(validateResize);
angular.element($window).on('resize', onWindowResize);
});
scope.$on('$destroy', function() {
angular.element($window).off('resize', onWindowResize);
scrollCtrl.$element && scrollCtrl.$element.off('scroll.init', refreshDimensions);
angular.element($window).off('resize', debouncedOnResize);
computedStyleNode && computedStyleNode.parentNode &&
computedStyleNode.parentNode.removeChild(computedStyleNode);
@@ -215,20 +188,58 @@ function CollectionRepeatDirective($ionicCollectionManager, $parse, $window, $$r
}));
}
scope.$watchCollection(listGetter, function(newValue) {
newValue || (newValue = []);
if (!angular.isArray(newValue)) {
throw new Error("collection-repeat expected an array for '" + listExpr + "', " +
"but got a " + typeof value);
function initAfterItemsContainer() {
var container = angular.element(
scrollView.__content.querySelector('.collection-repeat-after-container')
);
// Put everything in the view after the repeater into a container.
if (!container.length) {
var elementIsAfterRepeater = false;
var afterNodes = [].filter.call(scrollView.__content.childNodes, function(node) {
if (ionic.DomUtil.contains(node, containerNode)) {
elementIsAfterRepeater = true;
return false;
}
return elementIsAfterRepeater;
});
container = angular.element('<span class="collection-repeat-after-container">');
if (scrollView.options.scrollingX) {
container.addClass('horizontal');
}
container.append(afterNodes);
scrollView.__content.appendChild(container[0]);
}
return container;
}
// Wait for this digest to end before refreshing everything.
$timeout(function() {
getRepeatManager().refreshData(newValue);
if (newValue.length) refreshDimensions();
}, 0, false);
});
function initDimensions() {
//Height and width have four 'modes':
//1) Computed Mode
// - Nothing is supplied, so we getComputedStyle() on one element in the list and use
// that width and height value for the width and height of every item. This is re-computed
// every resize.
//2) Constant Mode, Static Integer
// - The user provides a constant number for width or height, in pixels. We parse it,
// store it on the `value` field, and it never changes
//3) Constant Mode, Percent
// - The user provides a percent string for width or height. The getter for percent is
// stored on the `getValue()` field, and is re-evaluated once every resize. The result
// is stored on the `value` field.
//4) Dynamic Mode
// - The user provides a dynamic expression for the width or height. This is re-evaluated
// for every item, stored on the `.getValue()` field.
if (!heightExpr && !widthExpr) {
heightData.computed = widthData.computed = true;
} else {
if (heightExpr) {
parseDimensionAttr(heightExpr, heightData);
} else {
heightData.computed = true;
}
if (!widthExpr) widthExpr = '"100%"';
parseDimensionAttr(widthExpr, widthData);
}
}
// Make sure this resize actually changed the size of the screen
function validateResize() {
@@ -240,6 +251,8 @@ function CollectionRepeatDirective($ionicCollectionManager, $parse, $window, $$r
}
}
function refreshDimensions() {
if (!data.length) return;
if (heightData.computed || widthData.computed) {
computeStyleDimensions();
}
@@ -419,6 +432,10 @@ function RepeatManagerFactory($rootScope, $window, $$rAF) {
var nextItemId = 0;
var estimatedItemsAcross;
var scrollViewSetDimensions = isVertical ?
function() { scrollView.setDimensions(null, null, null, view.getContentSize(), true); } :
function() { scrollView.setDimensions(null, null, view.getContentSize(), null, true); };
// view is a mix of list/grid methods + static/dynamic methods.
// See bottom for implementations. Available methods:
//
@@ -467,8 +484,14 @@ function RepeatManagerFactory($rootScope, $window, $$rAF) {
repeaterBeforeSize += current[isVertical ? 'offsetTop' : 'offsetLeft'];
} while( ionic.DomUtil.contains(scrollView.__content, current = current.offsetParent) );
if (!scrollView.__clientHeight || !scrollView.__clientWidth) {
scrollView.__clientWidth = scrollView.__container.clientWidth;
scrollView.__clientHeight = scrollView.__container.clientHeight;
}
(view.onRefreshLayout || angular.noop)();
view.refreshDirection();
scrollViewSetDimensions();
// Create the pool of items for reuse, setting the size to (estimatedItemsOnScreen) * 2,
// plus the size of the renderBuffer.
@@ -481,19 +504,20 @@ function RepeatManagerFactory($rootScope, $window, $$rAF) {
isLayoutReady = true;
if (isLayoutReady && isDataReady) {
forceRerender();
// If the resize or latest data change caused the scrollValue to
// now be out of bounds, resize the scrollView.
if (scrollView.__scrollLeft > scrollView.__maxScrollLeft ||
scrollView.__scrollTop > scrollView.__maxScrollTop) {
scrollView.resize();
}
forceRerender(true);
}
};
this.refreshData = function(newData) {
data = newData;
(view.onRefreshData || angular.noop)();
isDataReady = true;
if (isLayoutReady && isDataReady) {
scrollView.resize();
forceRerender();
}
};
this.destroy = function() {
@@ -771,13 +795,6 @@ function RepeatManagerFactory($rootScope, $window, $$rAF) {
function DynamicViewType() {
var self = this;
var scrollViewSetDimensions = isVertical ?
function() {
scrollView.setDimensions(null, null, null, self.getContentSize(), true);
} :
function() {
scrollView.setDimensions(null, null, self.getContentSize(), null, true);
};
var debouncedScrollViewSetDimensions = ionic.debounce(scrollViewSetDimensions, 25, true);
var calculateDimensions = isGridView ? calculateDimensionsGrid : calculateDimensionsList;
var dimensionsIndex;