mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
feat(ionRefresher): allow custom text & icons
Closes #760 BREAKING CHANGE: on-refresh and on-refresh-opening are no longer on the ion-content directive. They are on the ion-refresher. In addition, on-refresh-opening has been renamed to on-pulling. Change your code from this: ```html <ion-content on-refresh="onRefresh()" on-refresh-opening="onRefreshOpening()"> <ion-refresher></ion-refresher> </ion-content> ``` To this: ```html <ion-content> <ion-refresher on-refresh="onRefresh()" on-pulling="onRefreshOpening()"> </ion-refresher> </ion-content> ```
This commit is contained in:
@@ -36,28 +36,36 @@ angular.module('ionic.ui.scroll')
|
||||
scrollView.resize();
|
||||
}
|
||||
|
||||
this.setRefresher = function(refresherScope, refresherElement) {
|
||||
var refresher = this.refresher = refresherElement;
|
||||
var refresherHeight = self.refresher.clientHeight || 0;
|
||||
scrollView.activatePullToRefresh(refresherHeight, function() {
|
||||
refresher.classList.add('active');
|
||||
refresherScope.$onRefreshOpening();
|
||||
}, function() {
|
||||
refresher.classList.remove('refreshing');
|
||||
refresher.classList.remove('active');
|
||||
}, function() {
|
||||
refresher.classList.add('refreshing');
|
||||
refresherScope.$onRefresh();
|
||||
});
|
||||
};
|
||||
|
||||
$timeout(function() {
|
||||
scrollView.run();
|
||||
|
||||
self.refresher = element.querySelector('.scroll-refresher');
|
||||
|
||||
// Activate pull-to-refresh
|
||||
if(self.refresher) {
|
||||
var refresherHeight = self.refresher.clientHeight || 0;
|
||||
scrollView.activatePullToRefresh(refresherHeight, function() {
|
||||
self.refresher.classList.add('active');
|
||||
$scope.$onRefreshOpening && $scope.$onRefreshOpening();
|
||||
}, function() {
|
||||
self.refresher.classList.remove('refreshing');
|
||||
self.refresher.classList.remove('active');
|
||||
}, function() {
|
||||
self.refresher.classList.add('refreshing');
|
||||
$scope.$onRefresh && $scope.$onRefresh();
|
||||
$scope.$parent.$broadcast('scroll.onRefresh');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}]);
|
||||
|
||||
})();
|
||||
|
||||
var popups = [];
|
||||
function showPopup() {
|
||||
var newPopupDeferred = $q.defer();
|
||||
$q.all(popups).then(showThisPopup);
|
||||
|
||||
popups.push(newPopupDeferred);
|
||||
|
||||
function showThisPopup() {
|
||||
popups.splice(popups.indexOf(newPopupDeferred.promise), 1);
|
||||
}
|
||||
}
|
||||
|
||||
117
js/ext/angular/src/directive/ionicContent.js
vendored
117
js/ext/angular/src/directive/ionicContent.js
vendored
@@ -86,15 +86,10 @@ function($parse, $timeout, $ionicScrollDelegate, $controller, $ionicBind) {
|
||||
|
||||
$ionicBind($scope, $attr, {
|
||||
//Use $ to stop onRefresh from recursively calling itself
|
||||
//DEPRECATED, use <ion-infinite-scroll on-infinite-scroll="">
|
||||
$onRefresh: '&onRefresh',
|
||||
$onRefreshOpening: '&onRefreshOpening',
|
||||
$onScroll: '&onScroll',
|
||||
$onScrollComplete: '&onScrollComplete',
|
||||
//DEPRECATED, use <ion-infinite-scroll on-infinite-scroll="">
|
||||
$onInfiniteScroll: '&onInfiniteScroll',
|
||||
refreshComplete: '=',
|
||||
infiniteScrollDistance: '@',
|
||||
hasBouncing: '@',
|
||||
scroll: '@',
|
||||
padding: '@',
|
||||
@@ -154,8 +149,6 @@ function($parse, $timeout, $ionicScrollDelegate, $controller, $ionicBind) {
|
||||
if(attr.refreshComplete) {
|
||||
$scope.refreshComplete = function() {
|
||||
if($scope.scrollView) {
|
||||
scrollCtrl.refresher && scrollCtrl.refresher.classList.remove('active');
|
||||
scrollView.finishPullToRefresh();
|
||||
$scope.$parent.$broadcast('scroll.onRefreshComplete');
|
||||
}
|
||||
};
|
||||
@@ -175,24 +168,99 @@ function($parse, $timeout, $ionicScrollDelegate, $controller, $ionicBind) {
|
||||
};
|
||||
}])
|
||||
|
||||
.directive('ionRefresher', function() {
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ionRefresher
|
||||
* @module ionic
|
||||
* @restrict E
|
||||
* @parent ionContent, ionScroll
|
||||
* @description
|
||||
* Allows you to add pull-to-refresh to a scrollView.
|
||||
*
|
||||
* Place it as the first child of your {@link ionic.directive:ionContent} or
|
||||
* {@link ionic.directive:ionScroll} element.
|
||||
*
|
||||
* When refreshing is complete, $broadcast the 'scroll.refreshComplete' event
|
||||
* from your controller.
|
||||
*
|
||||
* @param {expression=} on-refresh Called when the user pulls down enough and lets go
|
||||
* of the refresher.
|
||||
* @param {expression=} on-pulling Called when the user starts to pull down
|
||||
* on the refresher.
|
||||
* @param {string=} pulling-icon The icon to display while the user is pulling down.
|
||||
* Default: 'ion-arrow-down-c'.
|
||||
* @param {string=} pulling-text The text to display while the user is pulling down.
|
||||
* @param {string=} refreshing-icon The icon to display after user lets go of the
|
||||
* refresher.
|
||||
* @param {string=} refreshing-text The text to display after the user lets go of
|
||||
* the refresher.
|
||||
*
|
||||
* @usage
|
||||
* ```html
|
||||
* <ion-content ng-controller="MyController">
|
||||
* <ion-refresher
|
||||
* pulling-text="Pull to refresh..."
|
||||
* on-refresh="doRefresh()">
|
||||
* </ion-refresher>
|
||||
* <ion-list>
|
||||
* <ion-item ng-repeat="item in items"></ion-item>
|
||||
* </ion-list>
|
||||
* </ion-content>
|
||||
* ```
|
||||
* ```js
|
||||
* angular.module('testApp', ['ionic'])
|
||||
* .controller('MyController', function($scope, $http) {
|
||||
* $scope.items = [1,2,3];
|
||||
* $scope.doRefresh = function() {
|
||||
* $http.get('/new-items').success(function(newItems) {
|
||||
* $scope.items = newItems;
|
||||
* //Stop the ion-refresher from spinning
|
||||
* $scope.$broadcast('scroll.refreshComplete');
|
||||
* });
|
||||
* };
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
.directive('ionRefresher', ['$ionicBind', function($ionicBind) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
require: ['^?ionContent', '^?ionList'],
|
||||
template: '<div class="scroll-refresher"><div class="ionic-refresher-content"><i class="icon ion-arrow-down-c icon-pulling"></i><i class="icon ion-loading-d icon-refreshing"></i></div></div>',
|
||||
scope: true
|
||||
};
|
||||
})
|
||||
require: '^$ionicScroll',
|
||||
template:
|
||||
'<div class="scroll-refresher">' +
|
||||
'<div class="ionic-refresher-content">' +
|
||||
'<i class="icon {{pullingIcon}} icon-pulling"></i>' +
|
||||
'<span class="icon-pulling" ng-bind-html="pullingText"></span>' +
|
||||
'<i class="icon {{refreshingIcon}} icon-refreshing"></i>' +
|
||||
'<span class="icon-refreshing" ng-bind-html="refreshingText"></span>' +
|
||||
'</div>' +
|
||||
'</div>',
|
||||
compile: function($element, $attrs) {
|
||||
if (angular.isUndefined($attrs.pullingIcon)) {
|
||||
$attrs.$set('pullingIcon', 'ion-arrow-down-c');
|
||||
}
|
||||
if (angular.isUndefined($attrs.refreshingIcon)) {
|
||||
$attrs.$set('refreshingIcon', 'ion-loading-d');
|
||||
}
|
||||
return function($scope, $element, $attrs, scrollCtrl) {
|
||||
$ionicBind($scope, $attrs, {
|
||||
pullingIcon: '@',
|
||||
pullingText: '@',
|
||||
refreshingIcon: '@',
|
||||
refreshingText: '@',
|
||||
$onRefresh: '&onRefresh',
|
||||
$onRefreshOpening: '&onRefreshOpening'
|
||||
});
|
||||
|
||||
.directive('ionScrollRefresher', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
transclude: true,
|
||||
template: '<div class="scroll-refresher"><div class="scroll-refresher-content" ng-transclude></div></div>'
|
||||
scrollCtrl.setRefresher($scope, $element[0]);
|
||||
$scope.$on('scroll.refreshComplete', function() {
|
||||
$element[0].classList.remove('active');
|
||||
scrollCtrl.scrollView.finishPullToRefresh();
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
})
|
||||
}])
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
@@ -261,10 +329,7 @@ function($parse, $timeout, $ionicScrollDelegate, $controller, $ionicBind) {
|
||||
this.isLoading = false;
|
||||
this.scrollView = null; //given by link function
|
||||
this.getMaxScroll = function() {
|
||||
var dist = $attrs.distance ||
|
||||
//deprecated: allow infiniteScrollDistance from ionContent
|
||||
$scope.infiniteScrollDistance ||
|
||||
'1%';
|
||||
var dist = $attrs.distance || '1%';
|
||||
return dist.indexOf('%') > -1 ?
|
||||
this.scrollView.getScrollMax().top * (1 - parseInt(dist,10) / 100) :
|
||||
this.scrollView.getScrollMax().top - parseInt(dist, 10);
|
||||
@@ -292,9 +357,7 @@ function($parse, $timeout, $ionicScrollDelegate, $controller, $ionicBind) {
|
||||
scrollView.getValues().top >= infiniteScrollCtrl.getMaxScroll()) {
|
||||
$element[0].classList.add('active');
|
||||
infiniteScrollCtrl.isLoading = true;
|
||||
|
||||
//deprecated: allow $onInfiniteScroll from parent
|
||||
$scope.$apply($attrs.onInfinite || $scope.$onInfiniteScroll);
|
||||
$scope.$parent.$apply($attrs.onInfinite || '');
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ describe('$ionicScroll Controller', function() {
|
||||
expect($ionicScrollDelegate.register).toHaveBeenCalledWith(scope, ctrl.$element, ctrl.scrollView);
|
||||
}));
|
||||
|
||||
it('should not setup if no child .scroll-refresher', function() {
|
||||
it('should not activatePullToRefresh if setRefresher is not called', function() {
|
||||
setup();
|
||||
timeout.flush();
|
||||
expect(ctrl.refresher).toBeFalsy();
|
||||
@@ -77,16 +77,7 @@ describe('$ionicScroll Controller', function() {
|
||||
expect(ctrl.scrollView.activatePullToRefresh).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not setup ctrl.refresher until after timeout', function() {
|
||||
setup({
|
||||
el: angular.element('<div><div class="scroll-refresher"></div></div>')[0]
|
||||
});
|
||||
expect(ctrl.refresher).toBeUndefined();
|
||||
timeout.flush();
|
||||
expect(ctrl.refresher).toBe(ctrl.element.children[0]);
|
||||
});
|
||||
|
||||
it('should work with .scroll-refresher child and proper refresher', function() {
|
||||
it('should activatePullToRefresh and work when setRefresher', function() {
|
||||
var startCb, refreshingCb, doneCb, refresherEl;
|
||||
setup({
|
||||
el: angular.element('<div><div class="scroll-refresher"></div></div>')[0]
|
||||
@@ -96,6 +87,9 @@ describe('$ionicScroll Controller', function() {
|
||||
refreshingCb = refreshing;
|
||||
doneCb = done;
|
||||
});
|
||||
ctrl.setRefresher(scope, ctrl.element);
|
||||
|
||||
var scrollOnRefreshSpy = jasmine.createSpy('scroll.onRefresh');
|
||||
|
||||
scope.$onRefresh = jasmine.createSpy('onRefresh');
|
||||
scope.$onRefreshOpening = jasmine.createSpy('onRefreshOpening');
|
||||
|
||||
@@ -84,16 +84,6 @@ describe('ionicInfiniteScroll directive', function() {
|
||||
var el = setup('distance=5%');
|
||||
expect(ctrl.getMaxScroll()).toBe(101 * 0.95);
|
||||
});
|
||||
|
||||
it('getMaxScroll should use scope.infiniteScrolDistance as number', function() {
|
||||
var el = setup('', { infiniteScrollDistance: '11' });
|
||||
expect(ctrl.getMaxScroll()).toBe(90);
|
||||
});
|
||||
|
||||
it('getMaxScroll should use scope.infiniteScrolDistance as percent', function() {
|
||||
var el = setup('', { infiniteScrollDistance: '50%' });
|
||||
expect(ctrl.getMaxScroll()).toBe(101 * 0.5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('scroll event', function() {
|
||||
@@ -107,16 +97,6 @@ describe('ionicInfiniteScroll directive', function() {
|
||||
expect(ctrl.isLoading).toBe(true);
|
||||
expect(el.scope().foo).toBe(1);
|
||||
});
|
||||
it('should add active and call $scope.$onInfiniteScroll if past top', function() {
|
||||
var onScrollSpy = jasmine.createSpy('onInfiniteScroll');
|
||||
var el = setup('', { $onInfiniteScroll: onScrollSpy });
|
||||
scrollTopValue = scrollMaxValue;
|
||||
el.controller('$ionicScroll').$element.triggerHandler('scroll');
|
||||
|
||||
expect(el.hasClass('active')).toBe(true);
|
||||
expect(ctrl.isLoading).toBe(true);
|
||||
expect(onScrollSpy).toHaveBeenCalled();
|
||||
});
|
||||
it('should not run the event twice if isLoading is true', function() {
|
||||
var onScrollSpy = jasmine.createSpy('onInfiniteScroll');
|
||||
var el = setup('', { $onInfiniteScroll: onScrollSpy });
|
||||
|
||||
102
js/ext/angular/test/directive/ionicRefresher.unit.js
Normal file
102
js/ext/angular/test/directive/ionicRefresher.unit.js
Normal file
@@ -0,0 +1,102 @@
|
||||
describe('ionRefresher directive', function() {
|
||||
beforeEach(module('ionic'));
|
||||
function setup(attrs, scopeProps) {
|
||||
var el;
|
||||
inject(function($compile, $rootScope) {
|
||||
var scope = $rootScope.$new();
|
||||
var ionicScrollCtrl = {
|
||||
setRefresher: jasmine.createSpy('setRefresher'),
|
||||
scrollView: {
|
||||
finishPullToRefresh: jasmine.createSpy('finishPullToRefresh')
|
||||
}
|
||||
};
|
||||
|
||||
angular.extend(scope, scopeProps || {});
|
||||
|
||||
el = angular.element('<ion-refresher '+(attrs||'')+'></ion-refresher>');
|
||||
el.data('$$ionicScrollController', ionicScrollCtrl);
|
||||
|
||||
$compile(el)(scope);
|
||||
$rootScope.$apply();
|
||||
});
|
||||
return el;
|
||||
}
|
||||
|
||||
it('should error without ionicScroll', inject(function($compile, $rootScope) {
|
||||
expect(function() {
|
||||
$compile('<ion-refresher>')($rootScope);
|
||||
}).toThrow();
|
||||
}));
|
||||
|
||||
it('should bind $onRefresh', function() {
|
||||
var refreshSpy = jasmine.createSpy('onRefresh');
|
||||
var el = setup('on-refresh="refreshSpy()"', {
|
||||
refreshSpy: refreshSpy
|
||||
});
|
||||
expect(refreshSpy).not.toHaveBeenCalled();
|
||||
el.scope().$onRefresh();
|
||||
expect(refreshSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should bind $onRefreshOpening', function() {
|
||||
var spyMe = jasmine.createSpy('onRefreshOpening');
|
||||
var el = setup('on-refresh-opening="spyMe()"', {
|
||||
spyMe: spyMe
|
||||
});
|
||||
expect(spyMe).not.toHaveBeenCalled();
|
||||
el.scope().$onRefreshOpening();
|
||||
expect(spyMe).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should setRefresher on scrollCtrl', function() {
|
||||
var el = setup();
|
||||
expect(el.controller('$ionicScroll').setRefresher.callCount).toBe(1);
|
||||
expect(el.controller('$ionicScroll').setRefresher).toHaveBeenCalledWith(
|
||||
el.scope(), el[0]
|
||||
)
|
||||
});
|
||||
|
||||
it('should listen for scroll.refreshComplete', function() {
|
||||
var el = setup();
|
||||
el.addClass('active');
|
||||
var ctrl = el.controller('$ionicScroll');
|
||||
expect(ctrl.scrollView.finishPullToRefresh).not.toHaveBeenCalled();
|
||||
el.scope().$broadcast('scroll.refreshComplete');
|
||||
expect(el.hasClass('active')).toBe(false);
|
||||
expect(ctrl.scrollView.finishPullToRefresh).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should have default pullingIcon', function() {
|
||||
var el = setup();
|
||||
expect(el[0].querySelector('.icon.icon-pulling.ion-arrow-down-c')).toBeTruthy();
|
||||
});
|
||||
it('should allow custom pullingIcon', function() {
|
||||
var el = setup('pulling-icon="super-icon"');
|
||||
expect(el[0].querySelector('.icon.icon-pulling.ion-arrow-down-c')).toBeFalsy();
|
||||
expect(el[0].querySelector('.icon.icon-pulling.super-icon')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should have default refreshingIcon', function() {
|
||||
var el = setup();
|
||||
expect(el[0].querySelector('.icon.icon-refreshing.ion-loading-d')).toBeTruthy();
|
||||
});
|
||||
it('should allow custom refreshingIcon', function() {
|
||||
var el = setup('refreshing-icon="monkey-icon"');
|
||||
expect(el[0].querySelector('.icon.icon-refreshing.ion-arrow-down-c')).toBeFalsy();
|
||||
expect(el[0].querySelector('.icon.icon-refreshing.monkey-icon')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should have no text by default', function() {
|
||||
var el = setup();
|
||||
expect(el.text().trim()).toBe('');
|
||||
});
|
||||
it('should allow pullingText', function() {
|
||||
var el = setup('pulling-text="{{2+2}} <b>some</b> text"');
|
||||
expect(el[0].querySelector('span.icon-pulling').innerHTML).toBe('4 <b>some</b> text');
|
||||
});
|
||||
it('should allow refreshingText', function() {
|
||||
var el = setup('refreshing-text="{{3+2}} <b>text</b>"');
|
||||
expect(el[0].querySelector('span.icon-refreshing').innerHTML).toBe('5 <b>text</b>');
|
||||
});
|
||||
|
||||
});
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
<ion-header-bar title="'Sample UL'" type="bar-positive"></ion-header-bar>
|
||||
|
||||
<ion-content on-infinite-scroll="addMore()" has-header="true" scroll="true" ng-controller="ContentCtrl" on-refresh="onRefresh()" has-footer="true" padding="false">
|
||||
<ion-content has-header="true" scroll="true" ng-controller="ContentCtrl" has-footer="true" padding="false">
|
||||
|
||||
<ion-refresher></ion-refresher>
|
||||
<ion-refresher on-refresh="onRefresh()"></ion-refresher>
|
||||
|
||||
<ul class="list">
|
||||
<li class="item">This ion-list should *exactly* fit</li>
|
||||
@@ -48,7 +48,7 @@
|
||||
<li ng-repeat="i in more">more {{$index}}</li>
|
||||
</ul>
|
||||
|
||||
<ion-infinite-scroll></ion-infinite-scroll>
|
||||
<ion-infinite-scroll on-infinite="addMore()"></ion-infinite-scroll>
|
||||
|
||||
</ion-content>
|
||||
|
||||
|
||||
@@ -211,7 +211,7 @@ body.grade-c {
|
||||
}
|
||||
|
||||
.ionic-refresher-content {
|
||||
.icon-pulling {
|
||||
.icon.icon-pulling {
|
||||
@include animation-name(refresh-spin);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user