mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
fix(collectionRepeat): fix data change while page disconnected, computed dimensions while no data
Closes #3240. Closes #3238.
This commit is contained in:
1
js/angular/controller/scrollController.js
vendored
1
js/angular/controller/scrollController.js
vendored
@@ -83,7 +83,6 @@ function($scope,
|
||||
|
||||
$timeout(function() {
|
||||
scrollView && scrollView.run && scrollView.run();
|
||||
$element.triggerHandler('scroll.init');
|
||||
});
|
||||
|
||||
self.getScrollView = function() {
|
||||
|
||||
171
js/angular/directive/collectionRepeat.js
vendored
171
js/angular/directive/collectionRepeat.js
vendored
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user