mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
feat(refresher): Allow refrsher to work with native scrolling
This update allows `<ion-refresher>` to work with native scrolling. Native scrolling can be enabled in the state deffinition, through the `$ionicConfigProvider` like `$ionicConfig.scrolling.jsScrolling(false);` or in the controller directly. It should function exactly the same as with JS scrolling enabled. This is a merge of the wip-scrolling branch.
This commit is contained in:
313
js/angular/controller/refresherController.js
vendored
Normal file
313
js/angular/controller/refresherController.js
vendored
Normal file
@@ -0,0 +1,313 @@
|
||||
IonicModule
|
||||
.controller('$ionicRefresher', [
|
||||
'$scope',
|
||||
'$attrs',
|
||||
'$element',
|
||||
'$ionicBind',
|
||||
'$timeout',
|
||||
function($scope, $attrs, $element, $ionicBind, $timeout) {
|
||||
var self = this,
|
||||
isDragging = false,
|
||||
isOverscrolling = false,
|
||||
dragOffset = 0,
|
||||
lastOverscroll = 0,
|
||||
ptrThreshold = 60,
|
||||
activated = false,
|
||||
scrollTime = 500,
|
||||
startY = null,
|
||||
deltaY = null,
|
||||
canOverscroll = true,
|
||||
scrollParent,
|
||||
scrollChild;
|
||||
|
||||
if (!isDefined($attrs.pullingIcon)) {
|
||||
$attrs.$set('pullingIcon', 'ion-android-arrow-down');
|
||||
}
|
||||
|
||||
$scope.showSpinner = !isDefined($attrs.refreshingIcon);
|
||||
|
||||
$ionicBind($scope, $attrs, {
|
||||
pullingIcon: '@',
|
||||
pullingText: '@',
|
||||
refreshingIcon: '@',
|
||||
refreshingText: '@',
|
||||
spinner: '@',
|
||||
disablePullingRotation: '@',
|
||||
$onRefresh: '&onRefresh',
|
||||
$onPulling: '&onPulling'
|
||||
});
|
||||
|
||||
function handleTouchend() {
|
||||
// if this wasn't an overscroll, get out immediately
|
||||
if (!canOverscroll && !isDragging) {
|
||||
return;
|
||||
}
|
||||
// reset Y
|
||||
startY = null;
|
||||
// the user has overscrolled but went back to native scrolling
|
||||
if (!isDragging) {
|
||||
dragOffset = 0;
|
||||
isOverscrolling = false;
|
||||
setScrollLock(false);
|
||||
return true;
|
||||
}
|
||||
isDragging = false;
|
||||
dragOffset = 0;
|
||||
|
||||
// the user has scroll far enough to trigger a refresh
|
||||
if (lastOverscroll > ptrThreshold) {
|
||||
start();
|
||||
scrollTo(ptrThreshold, scrollTime);
|
||||
|
||||
// the user has overscrolled but not far enough to trigger a refresh
|
||||
} else {
|
||||
scrollTo(0, scrollTime, deactivate);
|
||||
isOverscrolling = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function handleTouchmove(e) {
|
||||
// if multitouch or regular scroll event, get out immediately
|
||||
if (!canOverscroll || e.touches.length > 1) {
|
||||
return;
|
||||
}
|
||||
//if this is a new drag, keep track of where we start
|
||||
if (startY === null) {
|
||||
startY = parseInt(e.touches[0].screenY, 10);
|
||||
}
|
||||
|
||||
// how far have we dragged so far?
|
||||
deltaY = parseInt(e.touches[0].screenY, 10) - startY;
|
||||
|
||||
// if we've dragged up and back down in to native scroll territory
|
||||
if (deltaY - dragOffset <= 0 || scrollParent.scrollTop !== 0) {
|
||||
|
||||
if (isOverscrolling) {
|
||||
isOverscrolling = false;
|
||||
setScrollLock(false);
|
||||
}
|
||||
|
||||
if (isDragging) {
|
||||
nativescroll(scrollParent,parseInt(deltaY - dragOffset, 10) * -1);
|
||||
}
|
||||
|
||||
// if we're not at overscroll 0 yet, 0 out
|
||||
if (lastOverscroll !== 0) {
|
||||
overscroll(0);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} else if (deltaY > 0 && scrollParent.scrollTop === 0 && !isOverscrolling) {
|
||||
// starting overscroll, but drag started below scrollTop 0, so we need to offset the position
|
||||
dragOffset = deltaY;
|
||||
}
|
||||
|
||||
// prevent native scroll events while overscrolling
|
||||
e.preventDefault();
|
||||
|
||||
// if not overscrolling yet, initiate overscrolling
|
||||
if (!isOverscrolling) {
|
||||
isOverscrolling = true;
|
||||
setScrollLock(true);
|
||||
}
|
||||
|
||||
isDragging = true;
|
||||
// overscroll according to the user's drag so far
|
||||
overscroll(parseInt(deltaY - dragOffset, 10));
|
||||
|
||||
// update the icon accordingly
|
||||
if (!activated && lastOverscroll > ptrThreshold) {
|
||||
activated = true;
|
||||
ionic.requestAnimationFrame(activate);
|
||||
|
||||
} else if (activated && lastOverscroll < ptrThreshold) {
|
||||
activated = false;
|
||||
ionic.requestAnimationFrame(deactivate);
|
||||
}
|
||||
}
|
||||
|
||||
function handleScroll(e) {
|
||||
// canOverscrol is used to greatly simplify the drag handler during normal scrolling
|
||||
canOverscroll = (e.target.scrollTop === 0) || isDragging;
|
||||
}
|
||||
|
||||
function overscroll(val) {
|
||||
scrollChild.style[ionic.CSS.TRANSFORM] = 'translateY(' + val + 'px)';
|
||||
lastOverscroll = val;
|
||||
}
|
||||
|
||||
function nativescroll(target, newScrollTop) {
|
||||
// creates a scroll event that bubbles, can be cancelled, and with its view
|
||||
// and detail property initialized to window and 1, respectively
|
||||
target.scrollTop = newScrollTop;
|
||||
var e = document.createEvent("UIEvents");
|
||||
e.initUIEvent("scroll", true, true, window, 1);
|
||||
target.dispatchEvent(e);
|
||||
}
|
||||
|
||||
function setScrollLock(enabled) {
|
||||
// set the scrollbar to be position:fixed in preparation to overscroll
|
||||
// or remove it so the app can be natively scrolled
|
||||
if (enabled) {
|
||||
ionic.requestAnimationFrame(function() {
|
||||
scrollChild.classList.add('overscroll');
|
||||
show();
|
||||
});
|
||||
|
||||
} else {
|
||||
ionic.requestAnimationFrame(function() {
|
||||
scrollChild.classList.remove('overscroll');
|
||||
hide();
|
||||
deactivate();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$on('scroll.refreshComplete', function() {
|
||||
// prevent the complete from firing before the scroll has started
|
||||
$timeout(function() {
|
||||
|
||||
ionic.requestAnimationFrame(tail);
|
||||
|
||||
// scroll back to home during tail animation
|
||||
scrollTo(0, scrollTime, deactivate);
|
||||
|
||||
// return to native scrolling after tail animation has time to finish
|
||||
$timeout(function() {
|
||||
|
||||
if (isOverscrolling) {
|
||||
isOverscrolling = false;
|
||||
setScrollLock(false);
|
||||
}
|
||||
|
||||
}, scrollTime);
|
||||
|
||||
}, scrollTime);
|
||||
});
|
||||
|
||||
function scrollTo(Y, duration, callback) {
|
||||
// scroll animation loop w/ easing
|
||||
// credit https://gist.github.com/dezinezync/5487119
|
||||
var start = Date.now(),
|
||||
from = lastOverscroll;
|
||||
|
||||
if (from === Y) {
|
||||
callback();
|
||||
return; /* Prevent scrolling to the Y point if already there */
|
||||
}
|
||||
|
||||
// decelerating to zero velocity
|
||||
function easeOutCubic(t) {
|
||||
return (--t) * t * t + 1;
|
||||
}
|
||||
|
||||
// scroll loop
|
||||
function scroll() {
|
||||
var currentTime = Date.now(),
|
||||
time = Math.min(1, ((currentTime - start) / duration)),
|
||||
// where .5 would be 50% of time on a linear scale easedT gives a
|
||||
// fraction based on the easing method
|
||||
easedT = easeOutCubic(time);
|
||||
|
||||
overscroll(parseInt((easedT * (Y - from)) + from, 10));
|
||||
|
||||
if (time < 1) {
|
||||
ionic.requestAnimationFrame(scroll);
|
||||
|
||||
} else {
|
||||
|
||||
if (Y < 5 && Y > -5) {
|
||||
isOverscrolling = false;
|
||||
setScrollLock(false);
|
||||
}
|
||||
|
||||
callback && callback();
|
||||
}
|
||||
}
|
||||
|
||||
// start scroll loop
|
||||
ionic.requestAnimationFrame(scroll);
|
||||
}
|
||||
|
||||
|
||||
self.init = function() {
|
||||
scrollParent = $element.parent().parent()[0];
|
||||
scrollChild = $element.parent()[0];
|
||||
|
||||
if (!scrollParent.classList.contains('ionic-scroll') ||
|
||||
!scrollChild.classList.contains('scroll')) {
|
||||
throw new Error('Refresher must be immediate child of ion-content or ion-scroll');
|
||||
}
|
||||
|
||||
ionic.on('touchmove', handleTouchmove, scrollChild);
|
||||
ionic.on('touchend', handleTouchend, scrollChild);
|
||||
ionic.on('scroll', handleScroll, scrollParent);
|
||||
};
|
||||
|
||||
|
||||
$scope.$on('$destroy', destroy);
|
||||
|
||||
function destroy() {
|
||||
ionic.off('dragdown', handleTouchmove, scrollChild);
|
||||
ionic.off('dragend', handleTouchend, scrollChild);
|
||||
ionic.off('scroll', handleScroll, scrollParent);
|
||||
scrollParent = null;
|
||||
scrollChild = null;
|
||||
}
|
||||
|
||||
// DOM manipulation and broadcast methods shared by JS and Native Scrolling
|
||||
// getter used by JS Scrolling
|
||||
self.getRefresherDomMethods = function() {
|
||||
return {
|
||||
activate: activate,
|
||||
deactivate: deactivate,
|
||||
start: start,
|
||||
show: show,
|
||||
hide: hide,
|
||||
tail: tail
|
||||
};
|
||||
};
|
||||
|
||||
function activate() {
|
||||
$element[0].classList.add('active');
|
||||
$scope.$onPulling();
|
||||
}
|
||||
|
||||
function deactivate() {
|
||||
// give tail 150ms to finish
|
||||
$timeout(function() {
|
||||
// deactivateCallback
|
||||
$element.removeClass('active refreshing refreshing-tail');
|
||||
if (activated) activated = false;
|
||||
}, 150);
|
||||
}
|
||||
|
||||
function start() {
|
||||
// startCallback
|
||||
$element[0].classList.add('refreshing');
|
||||
$scope.$onRefresh();
|
||||
}
|
||||
|
||||
function show() {
|
||||
// showCallback
|
||||
$element[0].classList.remove('invisible');
|
||||
}
|
||||
|
||||
function hide() {
|
||||
// showCallback
|
||||
$element[0].classList.add('invisible');
|
||||
}
|
||||
|
||||
function tail() {
|
||||
// tailCallback
|
||||
$element[0].classList.add('refreshing-tail');
|
||||
}
|
||||
|
||||
// for testing
|
||||
self.__handleTouchmove = handleTouchmove;
|
||||
self.__getScrollChild = function() { return scrollChild; };
|
||||
self.__getScrollParent= function() { return scrollParent; };
|
||||
}
|
||||
]);
|
||||
52
js/angular/controller/scrollController.js
vendored
52
js/angular/controller/scrollController.js
vendored
@@ -12,7 +12,16 @@ IonicModule
|
||||
'$document',
|
||||
'$ionicScrollDelegate',
|
||||
'$ionicHistory',
|
||||
function($scope, scrollViewOptions, $timeout, $window, $location, $document, $ionicScrollDelegate, $ionicHistory) {
|
||||
'$controller',
|
||||
function($scope,
|
||||
scrollViewOptions,
|
||||
$timeout,
|
||||
$window,
|
||||
$location,
|
||||
$document,
|
||||
$ionicScrollDelegate,
|
||||
$ionicHistory,
|
||||
$controller) {
|
||||
|
||||
var self = this;
|
||||
// for testing
|
||||
@@ -171,38 +180,17 @@ function($scope, scrollViewOptions, $timeout, $window, $location, $document, $io
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
self._setRefresher = function(refresherScope, refresherElement) {
|
||||
var refresher = self.refresher = refresherElement;
|
||||
self._setRefresher = function(
|
||||
refresherScope,
|
||||
refresherElement,
|
||||
refresherMethods
|
||||
) {
|
||||
self.refresher = refresherElement;
|
||||
var refresherHeight = self.refresher.clientHeight || 60;
|
||||
scrollView.activatePullToRefresh(refresherHeight, function() {
|
||||
// activateCallback
|
||||
refresher.classList.add('active');
|
||||
refresherScope.$onPulling();
|
||||
onPullProgress(1);
|
||||
}, function() {
|
||||
// deactivateCallback
|
||||
refresher.classList.remove('active');
|
||||
refresher.classList.remove('refreshing');
|
||||
refresher.classList.remove('refreshing-tail');
|
||||
}, function() {
|
||||
// startCallback
|
||||
refresher.classList.add('refreshing');
|
||||
refresherScope.$onRefresh();
|
||||
}, function() {
|
||||
// showCallback
|
||||
refresher.classList.remove('invisible');
|
||||
}, function() {
|
||||
// hideCallback
|
||||
refresher.classList.add('invisible');
|
||||
}, function() {
|
||||
// tailCallback
|
||||
refresher.classList.add('refreshing-tail');
|
||||
}, onPullProgress);
|
||||
|
||||
function onPullProgress(progress) {
|
||||
$scope.$broadcast('$ionicRefresher.pullProgress', progress);
|
||||
refresherScope.$onPullProgress && refresherScope.$onPullProgress(progress);
|
||||
}
|
||||
scrollView.activatePullToRefresh(
|
||||
refresherHeight,
|
||||
refresherMethods
|
||||
);
|
||||
};
|
||||
|
||||
}]);
|
||||
|
||||
59
js/angular/directive/refresher.js
vendored
59
js/angular/directive/refresher.js
vendored
@@ -48,10 +48,6 @@
|
||||
* of the refresher.
|
||||
* @param {expression=} on-pulling Called when the user starts to pull down
|
||||
* on the refresher.
|
||||
* @param {expression=} on-pull-progress Repeatedly called as the user is pulling down
|
||||
* the refresher. The callback should have a `progress` argument which will be a number
|
||||
* from `0` and `1`. For example, if the user has pulled the refresher halfway
|
||||
* down, its progress would be `0.5`.
|
||||
* @param {string=} pulling-icon The icon to display while the user is pulling down.
|
||||
* Default: 'ion-android-arrow-down'.
|
||||
* @param {string=} spinner The {@link ionic.directive:ionSpinner} icon to display
|
||||
@@ -64,13 +60,14 @@
|
||||
*
|
||||
*/
|
||||
IonicModule
|
||||
.directive('ionRefresher', ['$ionicBind', '$parse', function($ionicBind, $parse) {
|
||||
.directive('ionRefresher', [function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
require: '^$ionicScroll',
|
||||
require: ['?^$ionicScroll', 'ionRefresher'],
|
||||
controller: '$ionicRefresher',
|
||||
template:
|
||||
'<div class="scroll-refresher" collection-repeat-ignore>' +
|
||||
'<div class="scroll-refresher invisible" collection-repeat-ignore>' +
|
||||
'<div class="ionic-refresher-content" ' +
|
||||
'ng-class="{\'ionic-refresher-with-text\': pullingText || refreshingText}">' +
|
||||
'<div class="icon-pulling" ng-class="{\'pulling-rotation-disabled\':disablePullingRotation}">' +
|
||||
@@ -84,38 +81,32 @@ IonicModule
|
||||
'<div class="text-refreshing" ng-bind-html="refreshingText"></div>' +
|
||||
'</div>' +
|
||||
'</div>',
|
||||
link: function($scope, $element, $attrs, scrollCtrl) {
|
||||
if (angular.isUndefined($attrs.pullingIcon)) {
|
||||
$attrs.$set('pullingIcon', 'ion-android-arrow-down');
|
||||
}
|
||||
$scope.showSpinner = angular.isUndefined($attrs.refreshingIcon);
|
||||
link: function($scope, $element, $attrs, ctrls) {
|
||||
|
||||
$ionicBind($scope, $attrs, {
|
||||
pullingIcon: '@',
|
||||
pullingText: '@',
|
||||
refreshingIcon: '@',
|
||||
refreshingText: '@',
|
||||
spinner: '@',
|
||||
disablePullingRotation: '@',
|
||||
$onRefresh: '&onRefresh',
|
||||
$onPulling: '&onPulling'
|
||||
});
|
||||
// JS Scrolling uses the scroll controller
|
||||
var scrollCtrl = ctrls[0],
|
||||
refresherCtrl = ctrls[1];
|
||||
|
||||
if (isDefined($attrs.onPullProgress)) {
|
||||
var onPullProgressFn = $parse($attrs.onPullProgress);
|
||||
$scope.$onPullProgress = function(progress) {
|
||||
onPullProgressFn($scope, {
|
||||
progress: progress
|
||||
if (!!scrollCtrl) {
|
||||
$element[0].classList.add('js-scrolling');
|
||||
|
||||
scrollCtrl._setRefresher(
|
||||
$scope,
|
||||
$element[0],
|
||||
refresherCtrl.getRefresherDomMethods()
|
||||
);
|
||||
|
||||
$scope.$on('scroll.refreshComplete', function() {
|
||||
$scope.$evalAsync(function() {
|
||||
scrollCtrl.scrollView.finishPullToRefresh();
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
} else {
|
||||
// Kick off native scrolling
|
||||
refresherCtrl.init();
|
||||
}
|
||||
|
||||
scrollCtrl._setRefresher($scope, $element[0]);
|
||||
$scope.$on('scroll.refreshComplete', function() {
|
||||
$scope.$evalAsync(function() {
|
||||
scrollCtrl.scrollView.finishPullToRefresh();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
@@ -538,9 +538,6 @@ ionic.views.Scroll = ionic.views.View.inherit({
|
||||
/** Callback to execute to start the actual refresh. Call {@link #refreshFinish} when done */
|
||||
__refreshStart: null,
|
||||
|
||||
/** Callback to state the progress while pulling to refresh */
|
||||
__refreshPullProgress: null,
|
||||
|
||||
/** Zoom level */
|
||||
__zoomLevel: 1,
|
||||
|
||||
@@ -1347,17 +1344,16 @@ ionic.views.Scroll = ionic.views.View.inherit({
|
||||
* @param tailCallback {Function} Callback to execute just before the refresher returns to it's original state. This is for zooming out the refresher.
|
||||
* @param pullProgressCallback Callback to state the progress while pulling to refresh
|
||||
*/
|
||||
activatePullToRefresh: function(height, activateCallback, deactivateCallback, startCallback, showCallback, hideCallback, tailCallback, pullProgressCallback) {
|
||||
activatePullToRefresh: function(height, refresherMethods) {
|
||||
var self = this;
|
||||
|
||||
self.__refreshHeight = height;
|
||||
self.__refreshActivate = function() {ionic.requestAnimationFrame(activateCallback);};
|
||||
self.__refreshDeactivate = function() {ionic.requestAnimationFrame(deactivateCallback);};
|
||||
self.__refreshStart = function() {ionic.requestAnimationFrame(startCallback);};
|
||||
self.__refreshShow = function() {ionic.requestAnimationFrame(showCallback);};
|
||||
self.__refreshHide = function() {ionic.requestAnimationFrame(hideCallback);};
|
||||
self.__refreshTail = function() {ionic.requestAnimationFrame(tailCallback);};
|
||||
self.__refreshPullProgress = pullProgressCallback;
|
||||
self.__refreshActivate = function() {ionic.requestAnimationFrame(refresherMethods.activate);};
|
||||
self.__refreshDeactivate = function() {ionic.requestAnimationFrame(refresherMethods.deactivate);};
|
||||
self.__refreshStart = function() {ionic.requestAnimationFrame(refresherMethods.start);};
|
||||
self.__refreshShow = function() {ionic.requestAnimationFrame(refresherMethods.show);};
|
||||
self.__refreshHide = function() {ionic.requestAnimationFrame(refresherMethods.hide);};
|
||||
self.__refreshTail = function() {ionic.requestAnimationFrame(refresherMethods.tail);};
|
||||
self.__refreshTailTime = 100;
|
||||
self.__minSpinTime = 600;
|
||||
},
|
||||
@@ -1850,9 +1846,6 @@ ionic.views.Scroll = ionic.views.View.inherit({
|
||||
self.__refreshDeactivate();
|
||||
}
|
||||
|
||||
} else if (!self.__refreshActive && self.__refreshPullProgress) {
|
||||
self.__refreshPullProgress(scrollTop / -self.__refreshHeight);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
overflow: hidden;
|
||||
margin: auto;
|
||||
height: 60px;
|
||||
|
||||
.ionic-refresher-content {
|
||||
position: absolute;
|
||||
bottom: 15px;
|
||||
@@ -80,6 +79,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.overflow-scroll > .scroll{
|
||||
&.overscroll{
|
||||
position:fixed;
|
||||
}
|
||||
-webkit-overflow-scrolling:touch;
|
||||
width:100%;
|
||||
}
|
||||
|
||||
@-webkit-keyframes refresh-spin {
|
||||
0% { -webkit-transform: translate3d(0,0,0) rotate(0); }
|
||||
|
||||
@@ -10,13 +10,11 @@
|
||||
<body ng-controller="MyCtrl">
|
||||
|
||||
<ion-header-bar class="bar-positive">
|
||||
<h1 class="title">Pull To Refresh</h1>
|
||||
<h1 class="title" id="test">Pull To Refresh</h1>
|
||||
</ion-header-bar>
|
||||
|
||||
<ion-content>
|
||||
<ion-refresher on-refresh="doRefresh()">
|
||||
|
||||
</ion-refresher>
|
||||
<ion-refresher on-refresh="doRefresh()"></ion-refresher>
|
||||
<ion-list>
|
||||
<ion-item ng-repeat="item in items">{{item}}</ion-item>
|
||||
</ion-list>
|
||||
@@ -24,18 +22,20 @@
|
||||
<script src="../../dist/js/ionic.bundle.js"></script>
|
||||
<script>
|
||||
angular.module('ionicApp', ['ionic'])
|
||||
|
||||
.controller('MyCtrl', function($scope, $timeout) {
|
||||
$scope.items = ['Item 1', 'Item 2', 'Item 3'];
|
||||
$scope.doRefresh = function() {
|
||||
$timeout( function() {
|
||||
//simulate async response
|
||||
$scope.items.push('New Item ' + Math.floor(Math.random() * 1000) + 4);
|
||||
//Stop the ion-refresher from spinning
|
||||
$scope.$broadcast('scroll.refreshComplete');
|
||||
}, 30);
|
||||
};
|
||||
});
|
||||
.config(function($ionicConfigProvider) {
|
||||
//$ionicConfigProvider.scrolling.jsScrolling(false);
|
||||
})
|
||||
.controller('MyCtrl', function($scope, $timeout) {
|
||||
$scope.items = ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5', 'Item 6','Item 7','Item 8','Item 9','Item 10'];
|
||||
$scope.doRefresh = function() {
|
||||
$timeout( function() {
|
||||
//simulate async response
|
||||
$scope.items.push('New Item ' + Math.floor(Math.random() * 1000) + 4);
|
||||
//Stop the ion-refresher from spinning
|
||||
$scope.$broadcast('scroll.refreshComplete');
|
||||
}, 500);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
129
test/unit/angular/controller/refreshController.unit.js
Normal file
129
test/unit/angular/controller/refreshController.unit.js
Normal file
@@ -0,0 +1,129 @@
|
||||
describe('$ionicRefresh Controller', function() {
|
||||
|
||||
beforeEach(module('ionic'));
|
||||
beforeEach(inject(function($ionicConfig) {
|
||||
ionic.Platform.ready = function(cb) { cb(); };
|
||||
ionic.requestAnimationFrame = function(cb) { cb(); };
|
||||
$ionicConfig.scrolling.jsScrolling(false);
|
||||
}));
|
||||
|
||||
function setup(options) {
|
||||
options = options || {};
|
||||
options.el = options.el ||
|
||||
angular.element('<ion-refresher></ion-refresher><div class="list"></div>')[0];
|
||||
scrollEl = options.parent ||
|
||||
angular.element('<div class="ionic-scroll"><div class="scroll"></div></div>');
|
||||
scrollEl.find('.scroll').append(options.el);
|
||||
|
||||
inject(function($controller, $rootScope, $timeout, $compile) {
|
||||
scope = $rootScope.$new();
|
||||
|
||||
$compile(scrollEl)(scope);
|
||||
scope.$apply();
|
||||
scope.$onRefresh = jasmine.createSpy('onRefresh');
|
||||
scope.$onPulling = jasmine.createSpy('onPulling');
|
||||
refresher = scrollEl.find('.scroll-refresher')[0];
|
||||
ctrl = angular.element(refresher).controller('ionRefresher');
|
||||
timeout = $timeout;
|
||||
});
|
||||
}
|
||||
function evt(y) {
|
||||
return {
|
||||
touches: [
|
||||
{screenY:y}
|
||||
],
|
||||
preventDefault: function() {}
|
||||
};
|
||||
}
|
||||
|
||||
it('should error if not child of scroll view', function() {
|
||||
setup({parent: angular.element('<div></div>')});
|
||||
expect(ctrl).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should oversroll using CSS transforms', function() {
|
||||
setup();
|
||||
|
||||
ctrl.__handleTouchmove(evt(0));
|
||||
ctrl.__handleTouchmove(evt(10));
|
||||
ctrl.__handleTouchmove(evt(20));
|
||||
expect(ctrl.__getScrollChild().style[ionic.CSS.TRANSFORM]).toBe('translateY(10px)');
|
||||
expect(ctrl.__getScrollChild().classList.contains('overscroll')).toBe(true);
|
||||
expect(refresher.classList.contains('invisible')).toBe(false);
|
||||
});
|
||||
|
||||
it('should resume native scrolling when overscroll is done', function() {
|
||||
setup();
|
||||
var domMethods = ctrl.getRefresherDomMethods();
|
||||
spyOn(domMethods, 'activate');
|
||||
spyOn(domMethods, 'deactivate');
|
||||
|
||||
ctrl.__handleTouchmove(evt(0));
|
||||
ctrl.__handleTouchmove(evt(10));
|
||||
expect(refresher.classList.contains('invisible')).toBe(false);
|
||||
ctrl.__handleTouchmove(evt(0));
|
||||
expect(ctrl.__getScrollChild().style[ionic.CSS.TRANSFORM]).toBe('translateY(0px)');
|
||||
expect(ctrl.__getScrollChild().classList.contains('overscroll')).toBe(false);
|
||||
expect(refresher.classList.contains('invisible')).toBe(true);
|
||||
});
|
||||
|
||||
it('should activate and deactivate when dragging past activation threshold', function() {
|
||||
setup();
|
||||
var domMethods = ctrl.getRefresherDomMethods();
|
||||
spyOn(domMethods, 'activate');
|
||||
spyOn(domMethods, 'deactivate');
|
||||
|
||||
ctrl.__handleTouchmove(evt(0));
|
||||
ctrl.__handleTouchmove(evt(10));
|
||||
ctrl.__handleTouchmove(evt(100));
|
||||
expect(ctrl.__getScrollChild().style[ionic.CSS.TRANSFORM]).toBe('translateY(90px)');
|
||||
expect(ctrl.__getScrollChild().classList.contains('overscroll')).toBe(true);
|
||||
expect(refresher.classList.contains('invisible')).toBe(false);
|
||||
expect(refresher.classList.contains('active')).toBe(true);
|
||||
|
||||
ctrl.__handleTouchmove(evt(0));
|
||||
timeout.flush();
|
||||
expect(ctrl.__getScrollChild().style[ionic.CSS.TRANSFORM]).toBe('translateY(0px)');
|
||||
expect(ctrl.__getScrollChild().classList.contains('overscroll')).toBe(false);
|
||||
expect(refresher.classList.contains('invisible')).toBe(true);
|
||||
expect(refresher.classList.contains('active')).toBe(false);
|
||||
});
|
||||
|
||||
it('should update refresher class when shared methods fire', function() {
|
||||
setup();
|
||||
|
||||
scope.$onRefresh = jasmine.createSpy('onRefresh');
|
||||
scope.$onPulling = jasmine.createSpy('onPulling');
|
||||
|
||||
|
||||
expect(refresher.classList.contains('active')).toBe(false);
|
||||
expect(refresher.classList.contains('refreshing')).toBe(false);
|
||||
expect(refresher.classList.contains('invisible')).toBe(true);
|
||||
expect(scope.$onRefresh).not.toHaveBeenCalled();
|
||||
expect(scope.$onPulling).not.toHaveBeenCalled();
|
||||
|
||||
ctrl.getRefresherDomMethods().show();
|
||||
expect(refresher.classList.contains('invisible')).toBe(false);
|
||||
|
||||
ctrl.getRefresherDomMethods().activate();
|
||||
expect(refresher.classList.contains('active')).toBe(true);
|
||||
expect(refresher.classList.contains('refreshing')).toBe(false);
|
||||
expect(scope.$onPulling).toHaveBeenCalled();
|
||||
|
||||
ctrl.getRefresherDomMethods().start();
|
||||
expect(refresher.classList.contains('refreshing')).toBe(true);
|
||||
expect(scope.$onRefresh).toHaveBeenCalled();
|
||||
|
||||
ctrl.getRefresherDomMethods().tail();
|
||||
expect(refresher.classList.contains('refreshing-tail')).toBe(true);
|
||||
|
||||
ctrl.getRefresherDomMethods().deactivate();
|
||||
timeout.flush();
|
||||
expect(refresher.classList.contains('active')).toBe(false);
|
||||
expect(refresher.classList.contains('refreshing')).toBe(false);
|
||||
expect(refresher.classList.contains('refreshing-tail')).toBe(false);
|
||||
|
||||
ctrl.getRefresherDomMethods().hide();
|
||||
expect(refresher.classList.contains('invisible')).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -242,19 +242,41 @@ describe('$ionicScroll Controller', function() {
|
||||
expect(ctrl.scrollView.activatePullToRefresh).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should activatePullToRefresh and work when setRefresher', function() {
|
||||
var startCb, refreshingCb, doneCb, refresherEl;
|
||||
it('should activatePullToRefresh and work when setRefresher', inject(function($compile) {
|
||||
var refresherEl,
|
||||
activateCB,
|
||||
deactivateCB,
|
||||
startCB,
|
||||
showCB,
|
||||
hideCB,
|
||||
tailCB,
|
||||
onPullProgressCB;
|
||||
setup({
|
||||
el: angular.element('<div><div class="scroll-refresher"></div></div>')[0]
|
||||
el: angular.element('<div><ion-refresher class="refresher"></ion-refresher></div>')[0]
|
||||
});
|
||||
spyOn(ctrl.scrollView, 'activatePullToRefresh').andCallFake(function(height, start, refreshing, done, show, hide) {
|
||||
startCb = start;
|
||||
refreshingCb = refreshing;
|
||||
doneCb = done;
|
||||
showCb = show;
|
||||
hideCb = hide;
|
||||
|
||||
$compile(ctrl.element)(scope);
|
||||
scope.$apply();
|
||||
|
||||
spyOn(ctrl.scrollView, 'activatePullToRefresh').andCallFake(function(height, start, done, refreshing, show, hide, tail, onPull) {
|
||||
activateCB = start;
|
||||
deactivateCB = done;
|
||||
startCB = refreshing;
|
||||
showCB = show;
|
||||
hideCB = hide;
|
||||
tailCB = tail;
|
||||
onPullProgressCB = onPull;
|
||||
});
|
||||
ctrl._setRefresher(scope, ctrl.element);
|
||||
|
||||
var refresher = ctrl.refresher;
|
||||
var refreshCtrl = angular.element(refresher).controller('ionRefresher');
|
||||
var dm = refreshCtrl.getRefresherDomMethods()
|
||||
|
||||
ctrl._setRefresher(
|
||||
scope,
|
||||
ctrl.element,
|
||||
dm
|
||||
);
|
||||
|
||||
var scrollOnRefreshSpy = jasmine.createSpy('scroll.onRefresh');
|
||||
|
||||
@@ -262,31 +284,29 @@ describe('$ionicScroll Controller', function() {
|
||||
scope.$onPulling = jasmine.createSpy('onPulling');
|
||||
|
||||
timeout.flush();
|
||||
var refresher = ctrl.refresher;
|
||||
|
||||
expect(refresher.classList.contains('active')).toBe(false);
|
||||
expect(refresher.classList.contains('refreshing')).toBe(false);
|
||||
|
||||
startCb();
|
||||
dm.activate();
|
||||
expect(refresher.classList.contains('active')).toBe(true);
|
||||
expect(refresher.classList.contains('refreshing')).toBe(false);
|
||||
expect(scope.$onPulling).toHaveBeenCalled();
|
||||
|
||||
refreshingCb();
|
||||
expect(refresher.classList.contains('refreshing')).toBe(false);
|
||||
|
||||
expect(scope.$onRefresh).not.toHaveBeenCalled();
|
||||
|
||||
doneCb();
|
||||
dm.start();
|
||||
expect(refresher.classList.contains('refreshing')).toBe(true);
|
||||
expect(scope.$onRefresh).toHaveBeenCalled();
|
||||
|
||||
dm.deactivate();
|
||||
timeout.flush();
|
||||
expect(refresher.classList.contains('active')).toBe(false);
|
||||
|
||||
showCb();
|
||||
dm.show();
|
||||
expect(refresher.classList.contains('invisible')).toBe(false);
|
||||
|
||||
hideCb();
|
||||
dm.hide();
|
||||
expect(refresher.classList.contains('invisible')).toBe(true);
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
[true, false].forEach(function(jsScrollingEnabled) {
|
||||
describe('ionRefresher directive', function() {
|
||||
|
||||
beforeEach(module('ionic'));
|
||||
beforeEach(inject(function($ionicConfig) {
|
||||
$ionicConfig.scrolling.jsScrolling(jsScrollingEnabled);
|
||||
}));
|
||||
function setup(attrs, scopeProps) {
|
||||
var el;
|
||||
inject(function($compile, $rootScope) {
|
||||
@@ -19,12 +22,13 @@ describe('ionRefresher directive', function() {
|
||||
|
||||
$compile(el)(scope);
|
||||
ionic.requestAnimationFrame = function() {};
|
||||
el.refresherCtrl = el.data('$ionRefresherController');
|
||||
$rootScope.$apply();
|
||||
});
|
||||
return el;
|
||||
}
|
||||
|
||||
it('should error without ionicScroll', inject(function($compile, $rootScope) {
|
||||
it('should error without ionScroll or ionContent', inject(function($compile, $rootScope) {
|
||||
expect(function() {
|
||||
$compile('<ion-refresher>')($rootScope);
|
||||
}).toThrow();
|
||||
@@ -54,7 +58,7 @@ describe('ionRefresher directive', function() {
|
||||
var el = setup();
|
||||
expect(el.controller('$ionicScroll')._setRefresher.callCount).toBe(1);
|
||||
expect(el.controller('$ionicScroll')._setRefresher).toHaveBeenCalledWith(
|
||||
el.scope(), el[0]
|
||||
el.scope(), el[0], el.refresherCtrl.getRefresherDomMethods()
|
||||
);
|
||||
});
|
||||
|
||||
@@ -108,3 +112,4 @@ describe('ionRefresher directive', function() {
|
||||
expect(el[0].querySelector('.pulling-rotation-disabled').innerHTML).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user