Skip to content

Commit

Permalink
feat($ionicLoading): on android, no back button action while loading
Browse files Browse the repository at this point in the history
Fixes #1273
  • Loading branch information
ajoslin committed May 5, 2014
1 parent 89a9ed1 commit fc8711c
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 142 deletions.
19 changes: 14 additions & 5 deletions js/angular/service/loading.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@ IonicModule
'$q',
'$log',
'$compile',
function($document, $ionicTemplateLoader, $ionicBackdrop, $timeout, $q, $log, $compile) {
'$ionicPlatform',
function($document, $ionicTemplateLoader, $ionicBackdrop, $timeout, $q, $log, $compile, $ionicPlatform) {

var loaderInstance;
//default value
//default values
var deregisterBackAction = angular.noop;
var loadingShowDelay = $q.when();

return {
Expand Down Expand Up @@ -79,7 +81,6 @@ function($document, $ionicTemplateLoader, $ionicBackdrop, $timeout, $q, $log, $c
appendTo: $document[0].body
})
.then(function(loader) {

var self = loader;

loader.show = function(options) {
Expand Down Expand Up @@ -136,21 +137,28 @@ function($document, $ionicTemplateLoader, $ionicBackdrop, $timeout, $q, $log, $c
$timeout.cancel(this.durationTimeout);
this.isShown = false;
};

return loader;
});
}
return $q.when(loaderInstance);
return loaderInstance;
}

function showLoader(options) {
options || (options = {});
var delay = options.delay || options.showDelay || 0;

//If loading.show() was called previously, cancel it and show with our new options
$timeout.cancel(loadingShowDelay);
loadingShowDelay && $timeout.cancel(loadingShowDelay);
loadingShowDelay = $timeout(angular.noop, delay);

loadingShowDelay.then(getLoader).then(function(loader) {
deregisterBackAction();
//Disable hardware back button while loading
deregisterBackAction = $ionicPlatform.registerBackButtonAction(
angular.noop,
PLATFORM_BACK_BUTTON_PRIORITY_LOADING
);
return loader.show(options);
});

Expand All @@ -168,6 +176,7 @@ function($document, $ionicTemplateLoader, $ionicBackdrop, $timeout, $q, $log, $c
}

function hideLoader() {
deregisterBackAction();
$timeout.cancel(loadingShowDelay);
getLoader().then(function(loader) {
loader.hide();
Expand Down
3 changes: 2 additions & 1 deletion js/angular/service/platform.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
var PLATFORM_BACK_BUTTON_PRIORITY_VIEW = 100;
var PLATFORM_BACK_BUTTON_PRIORITY_ACTION_SHEET = 300;
var PLATFORM_BACK_BUTTON_PRIORITY_POPUP = 500;
var PLATFORM_BACK_BUTTON_PRIORITY_POPUP = 400;
var PLATFORM_BACK_BUTTON_PRIORITY_LOADING = 500;
/**
* @ngdoc service
* @name $ionicPlatform
Expand Down
11 changes: 4 additions & 7 deletions test/html/loading.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,22 @@
<!-- Sets initial viewport load and disables zooming -->
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">

<link rel="stylesheet" href="../../../../dist/css/ionic.css">
<link rel="stylesheet" href="../../dist/css/ionic.css">

</head>
<body ng-controller="LoadingCtrl">
<button class="button button-dark" ng-click="startLoading()">Load</button>
<script src="../../../../dist/js/ionic.bundle.js"></script>
<script src="../../dist/js/ionic.bundle.js"></script>
<script>
angular.module('ionic.example', ['ionic'])

.controller('LoadingCtrl', function($scope, $ionicLoading) {
$scope.startLoading = function() {
$ionicLoading.show({
template: 'Getting current location...',
delay: 100
delay: 100,
duration: 500
});
$ionicLoading.show({
template: 'Getting current location...',
});
$ionicLoading.hide();
};
});
</script>
Expand Down
276 changes: 147 additions & 129 deletions test/unit/angular/service/loading.unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,151 +6,169 @@ describe('$ionicLoading service', function() {
expect(loader).toBe(loader2);
}));

describe('loader instance', function() {
describe('.show()', function() {

describe('.show()', function() {

it('should retain backdrop if !noBackdrop and !isShown', inject(function($ionicLoading, $ionicBackdrop) {
spyOn($ionicBackdrop, 'retain');
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
loader.show({});
expect($ionicBackdrop.retain).toHaveBeenCalled();
}));
it('should not retain backdrop if noBackdrop', inject(function($ionicLoading, $ionicBackdrop) {
spyOn($ionicBackdrop, 'retain');
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
loader.show({ noBackdrop: true });
expect($ionicBackdrop.retain).not.toHaveBeenCalled();
}));
it('should not retain backdrop if isShown', inject(function($ionicLoading, $ionicBackdrop) {
spyOn($ionicBackdrop, 'retain');
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
loader.isShown = true;
loader.show({});
expect($ionicBackdrop.retain).not.toHaveBeenCalled();
}));

it('should not timeout if no duration', inject(function($ionicLoading, $timeout) {
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
loader.show({});
expect(loader.durationTimeout).toBeFalsy();
}));
it('should timeout if duration', inject(function($ionicLoading, $timeout) {
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
loader.show({ duration: 1000 });
expect(loader.durationTimeout).toBeTruthy();
expect(loader.durationTimeout.$$timeoutId).toBeTruthy();
}));
it('should add active', inject(function($ionicLoading, $timeout) {
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
ionic.requestAnimationFrame = function(cb) { cb(); };
expect(loader.element.hasClass('active')).toBe(false);
loader.show({});
$timeout.flush();
expect(loader.element.hasClass('active')).toBe(true);
}));
it('should isShown = true', inject(function($ionicLoading) {
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
expect(loader.isShown).toBeFalsy();
loader.show({});
expect(loader.isShown).toBe(true);
}));

it('should use options.template', inject(function($ionicLoading, $rootScope) {
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
loader.show({ template: 'foo {{"bar"}}' });
$rootScope.$apply();
expect(loader.element.text()).toBe('foo bar');
}));

it('should use options.templateUrl', inject(function($ionicLoading, $rootScope, $ionicTemplateLoader, $q) {
spyOn($ionicTemplateLoader, 'load').andReturn($q.when('{{1}} content'));
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
loader.show({ templateUrl: 'template.html' });
expect($ionicTemplateLoader.load).toHaveBeenCalledWith('template.html');
$rootScope.$apply();
expect(loader.element.text()).toBe('1 content');
}));

});

describe('.hide()', function() {

it('should release backdrop if hasBackdrop and isShown', inject(function($ionicLoading, $ionicBackdrop) {
spyOn($ionicBackdrop, 'release');
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
loader.isShown = true;
loader.hasBackdrop = true;
loader.hide();
expect($ionicBackdrop.release).toHaveBeenCalled();
}));
it('should not release backdrop if !hasBackdrop', inject(function($ionicLoading, $ionicBackdrop) {
spyOn($ionicBackdrop, 'release');
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
loader.isShown = true;
loader.hide();
expect($ionicBackdrop.release).not.toHaveBeenCalled();
}));
it('should cancel durationTimeout and set isShown to false', inject(function($ionicLoading, $timeout) {
spyOn($timeout, 'cancel');
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
loader.durationTimeout = {};
loader.isShown = true;
loader.hide({});
expect($timeout.cancel).toHaveBeenCalledWith(loader.durationTimeout);
expect(loader.isShown).toBe(false);
}));

});
it('should retain backdrop if !noBackdrop and !isShown', inject(function($ionicLoading, $ionicBackdrop) {
spyOn($ionicBackdrop, 'retain');
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
loader.show({});
expect($ionicBackdrop.retain).toHaveBeenCalled();
}));
it('should not retain backdrop if noBackdrop', inject(function($ionicLoading, $ionicBackdrop) {
spyOn($ionicBackdrop, 'retain');
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
loader.show({ noBackdrop: true });
expect($ionicBackdrop.retain).not.toHaveBeenCalled();
}));
it('should not retain backdrop if isShown', inject(function($ionicLoading, $ionicBackdrop) {
spyOn($ionicBackdrop, 'retain');
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
loader.isShown = true;
loader.show({});
expect($ionicBackdrop.retain).not.toHaveBeenCalled();
}));

it('should show with options', inject(function($ionicLoading, $timeout) {
it('should not timeout if no duration', inject(function($ionicLoading, $timeout) {
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
spyOn(loader, 'show');
var options = {};
$ionicLoading.show(options);
loader.show({});
expect(loader.durationTimeout).toBeFalsy();
}));
it('should timeout if duration', inject(function($ionicLoading, $timeout) {
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
loader.show({ duration: 1000 });
expect(loader.durationTimeout).toBeTruthy();
expect(loader.durationTimeout.$$timeoutId).toBeTruthy();
}));
it('should add active', inject(function($ionicLoading, $timeout) {
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
ionic.requestAnimationFrame = function(cb) { cb(); };
expect(loader.element.hasClass('active')).toBe(false);
loader.show({});
$timeout.flush();
expect(loader.show).toHaveBeenCalledWith(options);
expect(loader.element.hasClass('active')).toBe(true);
}));
it('should isShown = true', inject(function($ionicLoading) {
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
expect(loader.isShown).toBeFalsy();
loader.show({});
expect(loader.isShown).toBe(true);
}));

it('should $timeout.cancel & hide', inject(function($ionicLoading, $rootScope, $timeout) {
it('should use options.template', inject(function($ionicLoading, $rootScope) {
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
spyOn($timeout, 'cancel');
spyOn(loader, 'hide');
$ionicLoading.hide();
expect($timeout.cancel).toHaveBeenCalled();
loader.show({ template: 'foo {{"bar"}}' });
$rootScope.$apply();
expect(loader.hide).toHaveBeenCalled();
expect(loader.element.text()).toBe('foo bar');
}));

it('hide should cancel show delay and just go ahead and hide', inject(function($ionicLoading, $timeout) {
ionic.requestAnimationFrame = function(cb) { cb(); };
it('should use options.templateUrl', inject(function($ionicLoading, $rootScope, $ionicTemplateLoader, $q) {
spyOn($ionicTemplateLoader, 'load').andReturn($q.when('{{1}} content'));
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
spyOn(loader, 'hide').andCallThrough();
spyOn(loader, 'show').andCallThrough();
$ionicLoading.show({ delay: 1000 });
$ionicLoading.hide();
expect(loader.show).not.toHaveBeenCalled();
expect(loader.hide).not.toHaveBeenCalled();
$timeout.flush();
expect(loader.show).not.toHaveBeenCalled();
expect(loader.hide).toHaveBeenCalled();
expect(loader.isShown).toBe(false);
expect(loader.element.hasClass('active')).toBe(false);
loader.show({ templateUrl: 'template.html' });
expect($ionicTemplateLoader.load).toHaveBeenCalledWith('template.html');
$rootScope.$apply();
expect(loader.element.text()).toBe('1 content');
}));
it('show should only active after raf is still isShown', inject(function($ionicLoading) {

});

describe('.hide()', function() {

it('should release backdrop if hasBackdrop and isShown', inject(function($ionicLoading, $ionicBackdrop) {
spyOn($ionicBackdrop, 'release');
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
var rafCallback;
ionic.requestAnimationFrame = function(cb) {
rafCallback = cb;
};
loader.show({});
expect(loader.isShown).toBe(true);
loader.isShown = true;
loader.hasBackdrop = true;
loader.hide();
expect($ionicBackdrop.release).toHaveBeenCalled();
}));
it('should not release backdrop if !hasBackdrop', inject(function($ionicLoading, $ionicBackdrop) {
spyOn($ionicBackdrop, 'release');
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
loader.isShown = true;
loader.hide();
expect($ionicBackdrop.release).not.toHaveBeenCalled();
}));
it('should cancel durationTimeout and set isShown to false', inject(function($ionicLoading, $timeout) {
spyOn($timeout, 'cancel');
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
loader.durationTimeout = {};
loader.isShown = true;
loader.hide({});
expect($timeout.cancel).toHaveBeenCalledWith(loader.durationTimeout);
expect(loader.isShown).toBe(false);
rafCallback();
expect(loader.element.hasClass('active')).toBe(false);
ionic.requestAnimationFrame = function(cb) { cb(); };
}));

});

it('should show with options', inject(function($ionicLoading, $timeout) {
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
spyOn(loader, 'show');
var options = {};
$ionicLoading.show(options);
$timeout.flush();
expect(loader.show).toHaveBeenCalledWith(options);
}));

it('should $timeout.cancel & hide', inject(function($ionicLoading, $rootScope, $timeout) {
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
spyOn($timeout, 'cancel');
spyOn(loader, 'hide');
$ionicLoading.hide();
expect($timeout.cancel).toHaveBeenCalled();
$rootScope.$apply();
expect(loader.hide).toHaveBeenCalled();
}));

it('hide should cancel show delay and just go ahead and hide', inject(function($ionicLoading, $timeout) {
ionic.requestAnimationFrame = function(cb) { cb(); };
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
spyOn(loader, 'hide').andCallThrough();
spyOn(loader, 'show').andCallThrough();
$ionicLoading.show({ delay: 1000 });
$ionicLoading.hide();
expect(loader.show).not.toHaveBeenCalled();
expect(loader.hide).not.toHaveBeenCalled();
$timeout.flush();
expect(loader.show).not.toHaveBeenCalled();
expect(loader.hide).toHaveBeenCalled();
expect(loader.isShown).toBe(false);
expect(loader.element.hasClass('active')).toBe(false);
}));
it('show should only active after raf is still isShown', inject(function($ionicLoading) {
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
var rafCallback;
ionic.requestAnimationFrame = function(cb) {
rafCallback = cb;
};
loader.show({});
expect(loader.isShown).toBe(true);
loader.hide();
expect(loader.isShown).toBe(false);
rafCallback();
expect(loader.element.hasClass('active')).toBe(false);
ionic.requestAnimationFrame = function(cb) { cb(); };
}));

describe("back button", function() {
it('.show() should register back button action', inject(function($ionicLoading, $ionicPlatform, $timeout) {
spyOn($ionicPlatform, 'registerBackButtonAction');
$ionicLoading.show();
$timeout.flush();
expect($ionicPlatform.registerBackButtonAction).toHaveBeenCalledWith(
angular.noop,
PLATFORM_BACK_BUTTON_PRIORITY_LOADING
);
}));
it('.hide() should deregister back button action', inject(function($ionicLoading, $ionicPlatform, $timeout) {
var deregisterSpy = jasmine.createSpy('deregister');
spyOn($ionicPlatform, 'registerBackButtonAction').andReturn(deregisterSpy);
$ionicLoading.show();
$timeout.flush();
expect(deregisterSpy).not.toHaveBeenCalled();
$ionicLoading.hide();
expect(deregisterSpy).toHaveBeenCalled();
}));
});

});

0 comments on commit fc8711c

Please sign in to comment.