Skip to content

Commit

Permalink
fix(popup): synchronously add/remove popups from stack, no matter the…
Browse files Browse the repository at this point in the history
… animation state

Closes #3131
  • Loading branch information
ajoslin committed Apr 16, 2015
1 parent 71e8971 commit 9baf219
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 113 deletions.
200 changes: 94 additions & 106 deletions js/angular/service/popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,84 +281,79 @@ function($ionicTemplateLoader, $ionicBackdrop, $q, $timeout, $rootScope, $ionicB
buttons: []
}, options || {});

var popupPromise = $ionicTemplateLoader.compile({
template: POPUP_TPL,
scope: options.scope && options.scope.$new(),
appendTo: $ionicBody.get()
var self = {};
self.scope = (options.scope || $rootScope).$new();
self.element = jqLite(POPUP_TPL);
self.responseDeferred = $q.defer();

$ionicBody.get().appendChild(self.element[0]);
$compile(self.element)(self.scope);

extend(self.scope, {
title: options.title,
buttons: options.buttons,
subTitle: options.subTitle,
cssClass: options.cssClass,
$buttonTapped: function(button, event) {
var result = (button.onTap || noop)(event);
event = event.originalEvent || event; //jquery events

if (!event.defaultPrevented) {
self.responseDeferred.resolve(result);
}
}
});
var contentPromise = options.templateUrl ?
$ionicTemplateLoader.load(options.templateUrl) :
$q.when(options.template || options.content || '');

return $q.all([popupPromise, contentPromise]).then(function(results) {
var self = results[0];
var content = results[1];
var responseDeferred = $q.defer();

self.responseDeferred = responseDeferred;

//Can't ng-bind-html for popup-body because it can be insecure html
//(eg an input in case of prompt)
var body = jqLite(self.element[0].querySelector('.popup-body'));
if (content) {
body.html(content);
$compile(body.contents())(self.scope);
$q.when(
options.templateUrl ?
$ionicTemplateLoader.load(options.templateUrl) :
(options.template || options.content || '')
).then(function(template) {
var popupBody = jqLite(self.element[0].querySelector('.popup-body'));
if (template) {
popupBody.html(template);
$compile(popupBody.contents())(self.scope);
} else {
body.remove();
popupBody.remove();
}
});

extend(self.scope, {
title: options.title,
buttons: options.buttons,
subTitle: options.subTitle,
cssClass: options.cssClass,
$buttonTapped: function(button, event) {
var result = (button.onTap || noop)(event);
event = event.originalEvent || event; //jquery events

if (!event.defaultPrevented) {
responseDeferred.resolve(result);
}
}
});

self.show = function() {
if (self.isShown || self.removed) return;
self.show = function() {
if (self.isShown || self.removed) return;

self.isShown = true;
ionic.requestAnimationFrame(function() {
//if hidden while waiting for raf, don't show
if (!self.isShown) return;
self.isShown = true;
ionic.requestAnimationFrame(function() {
//if hidden while waiting for raf, don't show
if (!self.isShown) return;

self.element.removeClass('popup-hidden');
self.element.addClass('popup-showing active');
focusInput(self.element);
});
};
self.element.removeClass('popup-hidden');
self.element.addClass('popup-showing active');
focusInput(self.element);
});
};

self.hide = function(callback) {
callback = callback || noop;
if (!self.isShown) return callback();
self.hide = function(callback) {
callback = callback || noop;
if (!self.isShown) return callback();

self.isShown = false;
self.element.removeClass('active');
self.element.addClass('popup-hidden');
$timeout(callback, 250);
};
self.isShown = false;
self.element.removeClass('active');
self.element.addClass('popup-hidden');
$timeout(callback, 250, false);
};

self.remove = function() {
if (self.removed) return;
self.remove = function() {
if (self.removed) return;

self.hide(function() {
self.element.remove();
self.scope.$destroy();
});
self.hide(function() {
self.element.remove();
self.scope.$destroy();
});

self.removed = true;
};
self.removed = true;
};

return self;
});
return self;
}

function onHardwareBackButton() {
Expand All @@ -367,69 +362,62 @@ function($ionicTemplateLoader, $ionicBackdrop, $q, $timeout, $rootScope, $ionicB
}

function showPopup(options) {
var resultDeferred;
var popupPromise = $ionicPopup._createPopup(options);
var popup = $ionicPopup._createPopup(options);
var showDelay = 0;

if (popupStack.length > 0) {
popupStack[popupStack.length - 1].hide();
resultDeferred = $timeout(doShowPopup, config.stackPushDelay);
showDelay = config.stackPushDelay;
} else {
//Add popup-open & backdrop if this is first popup
$ionicBody.addClass('popup-open');
console.log("RETAIN");
$ionicBackdrop.retain();
//only show the backdrop on the first popup
$ionicPopup._backButtonActionDone = $ionicPlatform.registerBackButtonAction(
onHardwareBackButton,
IONIC_BACK_PRIORITY.popup
);
resultDeferred = doShowPopup();
}

resultDeferred.close = function popupClose(result) {
popupPromise.then(function(popup) {
if (!popup.removed) popup.responseDeferred.resolve(result);
});
// Expose a 'close' method on the returned promise
popup.responseDeferred.promise.close = function popupClose(result) {
if (!popup.removed) popup.responseDeferred.resolve(result);
};
//DEPRECATED: notify the promise with an object with a close method
popup.responseDeferred.notify({ close: popup.responseDeferred.close });

return resultDeferred;
doShow();

function doShowPopup() {
return popupPromise.then(function(popup) {
popupStack.push(popup);
popup.show();
return popup.responseDeferred.promise;

//DEPRECATED: notify the promise with an object with a close method
popup.responseDeferred.notify({
close: resultDeferred.close
});
function doShow() {
popupStack.push(popup);
$timeout(popup.show, showDelay, false);

return popup.responseDeferred.promise.then(function(result) {
var index = popupStack.indexOf(popup);
if (index !== -1) {
popupStack.splice(index, 1);
}
popup.remove();
$ionicBackdrop.release();

if (popupStack.length > 0) {
popupStack[popupStack.length - 1].show();
} else {
//Remove popup-open & backdrop if this is last popup
$timeout(function() {
// wait to remove this due to a 300ms delay native
// click which would trigging whatever was underneath this
if (!popupStack.length) {
$ionicBody.removeClass('popup-open');
}
}, 400, false);
popup.responseDeferred.promise.then(function(result) {
var index = popupStack.indexOf(popup);
if (index !== -1) {
popupStack.splice(index, 1);
}

($ionicPopup._backButtonActionDone || noop)();
}
if (popupStack.length > 0) {
popupStack[popupStack.length - 1].show();
} else {
$ionicBackdrop.release();
//Remove popup-open & backdrop if this is last popup
$timeout(function() {
// wait to remove this due to a 300ms delay native
// click which would trigging whatever was underneath this
if (!popupStack.length) {
$ionicBody.removeClass('popup-open');
}
}, 400, false);
($ionicPopup._backButtonActionDone || noop)();
}

return result;
});
popup.remove();

return result;
});

}
Expand Down
14 changes: 7 additions & 7 deletions test/unit/angular/service/popup.unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ describe('$ionicPopup service', function() {
show: jasmine.createSpy('show'),
responseDeferred: $q.defer()
};
spyOn($ionicPopup, '_createPopup').andReturn($q.when(fakePopup));
spyOn($ionicPopup, '_createPopup').andReturn(fakePopup);
expect($ionicPopup._popupStack.length).toBe(0);
$ionicPopup.show();
expect(fakePopup.show).not.toHaveBeenCalled();
Expand All @@ -215,9 +215,9 @@ describe('$ionicPopup service', function() {
}));

it('should have close function which resolves promise with argument', inject(function($ionicPopup, $q, $rootScope) {
var popup = TestUtil.unwrapPromise($ionicPopup._createPopup());
spyOn($ionicPopup, '_createPopup').andReturn($q.when(popup));
var result = $ionicPopup.show();
var popup = TestUtil.unwrapPromise($ionicPopup._createPopup({template: 'foo'}));
spyOn($ionicPopup, '_createPopup').andReturn(popup);
var result = $ionicPopup.show();
spyOn(popup.responseDeferred, 'resolve');
result.close('foobar');
$rootScope.$apply();
Expand All @@ -230,7 +230,7 @@ describe('$ionicPopup service', function() {
remove: jasmine.createSpy('remove'),
responseDeferred: $q.defer()
};
spyOn($ionicPopup, '_createPopup').andReturn($q.when(fakePopup));
spyOn($ionicPopup, '_createPopup').andReturn(fakePopup);
var result = $ionicPopup.show();
$timeout.flush();
expect(fakePopup.remove).not.toHaveBeenCalled();
Expand All @@ -251,7 +251,7 @@ describe('$ionicPopup service', function() {
show: jasmine.createSpy('show'),
hide: jasmine.createSpy('hide')
};
spyOn($ionicPopup, '_createPopup').andReturn($q.when(fakePopup));
spyOn($ionicPopup, '_createPopup').andReturn(fakePopup);
$ionicPopup._popupStack.unshift(previousPopup);
$ionicPopup.show();
fakePopup.responseDeferred.resolve();
Expand All @@ -268,7 +268,7 @@ describe('$ionicPopup service', function() {
var backDoneSpy = jasmine.createSpy('backDone');
spyOn($ionicPlatform, 'registerBackButtonAction').andReturn(backDoneSpy);
spyOn($ionicBackdrop, 'release');
spyOn($ionicPopup, '_createPopup').andReturn($q.when(fakePopup));
spyOn($ionicPopup, '_createPopup').andReturn(fakePopup);
$ionicPopup.show();
fakePopup.responseDeferred.resolve();
$timeout.flush();
Expand Down

0 comments on commit 9baf219

Please sign in to comment.