');
var backdropHolds = 0;
+ var toggler = $animateClassToggler(el, 'ng-hide');
+
+ $document[0].body.appendChild(el[0]);
return {
retain: retain,
release: release,
- _getElement: getElement
+ // exposed for testing
+ _element: el
};
- function getElement() {
- if (!el) {
- el = angular.element('
');
- $document[0].body.appendChild(el[0]);
- }
- return el;
- }
function retain() {
if ( (++backdropHolds) === 1 ) {
- $animate.removeClass(getElement(), 'ng-hide');
+ toggler.removeClass();
}
}
function release() {
if ( (--backdropHolds) === 0 ) {
- $animate.addClass(getElement(), 'ng-hide');
+ toggler.addClass();
}
}
}]);
diff --git a/js/ext/angular/src/service/ionicLoading.js b/js/ext/angular/src/service/ionicLoading.js
index 2cd4231a5f..1b5d695c78 100644
--- a/js/ext/angular/src/service/ionicLoading.js
+++ b/js/ext/angular/src/service/ionicLoading.js
@@ -41,7 +41,8 @@ angular.module('ionic.service.loading', [])
'$q',
'$log',
'$compile',
-function($animate, $document, $ionicTemplateLoader, $ionicBackdrop, $timeout, $q, $log, $compile) {
+ '$animateClassToggler',
+function($animate, $document, $ionicTemplateLoader, $ionicBackdrop, $timeout, $q, $log, $compile, $animateClassToggler) {
var loaderInstance;
//default value
@@ -81,6 +82,9 @@ function($animate, $document, $ionicTemplateLoader, $ionicBackdrop, $timeout, $q
appendTo: $document[0].body
})
.then(function(loader) {
+
+ var toggler = $animateClassToggler(loader.element, 'ng-hide');
+
loader.show = function(options) {
var self = this;
var templatePromise = options.templateUrl ?
@@ -88,6 +92,7 @@ function($animate, $document, $ionicTemplateLoader, $ionicBackdrop, $timeout, $q
//options.content: deprecated
$q.when(options.template || options.content || '');
+
if (!this.isShown) {
//options.showBackdrop: deprecated
this.hasBackdrop = !options.noBackdrop && options.showBackdrop !== false;
@@ -111,26 +116,16 @@ function($animate, $document, $ionicTemplateLoader, $ionicBackdrop, $timeout, $q
}
});
+ toggler.removeClass();
+ ionic.DomUtil.centerElementByMarginTwice(this.element[0]);
this.isShown = true;
- ionic.requestAnimationFrame(function() {
- if (self.isShown) {
- $animate.removeClass(self.element, 'ng-hide');
- //Fix for ios: if we center the element twice, it always gets
- //position right. Otherwise, it doesn't
- ionic.DomUtil.centerElementByMargin(self.element[0]);
- //One frame after it's visible, position it
- ionic.requestAnimationFrame(function() {
- ionic.DomUtil.centerElementByMargin(self.element[0]);
- });
- }
- });
};
loader.hide = function() {
if (this.isShown) {
if (this.hasBackdrop) {
$ionicBackdrop.release();
}
- $animate.addClass(this.element, 'ng-hide');
+ toggler.addClass();
}
$timeout.cancel(this.durationTimeout);
this.isShown = false;
diff --git a/js/ext/angular/src/service/ionicPopup.js b/js/ext/angular/src/service/ionicPopup.js
index 8bd3cc17a3..beb212e84f 100644
--- a/js/ext/angular/src/service/ionicPopup.js
+++ b/js/ext/angular/src/service/ionicPopup.js
@@ -327,14 +327,8 @@ function($animate, $ionicTemplateLoader, $ionicBackdrop, $log, $q, $timeout, $ro
self.element.removeClass('popup-hidden');
self.element.addClass('popup-showing active');
+ ionic.DomUtil.centerElementByMarginTwice(self.element[0]);
focusLastButton(self.element);
- //Fix for ios: if we center the element twice, it always gets
- //position right. Otherwise, it doesn't
- ionic.DomUtil.centerElementByMargin(self.element[0]);
- //One frame after it's visible, position it
- ionic.requestAnimationFrame(function() {
- ionic.DomUtil.centerElementByMargin(self.element[0]);
- });
});
};
self.hide = function(callback) {
diff --git a/js/ext/angular/test/directive/ionicBackdrop.unit.js b/js/ext/angular/test/directive/ionicBackdrop.unit.js
index 8000af283c..47c7c9f604 100644
--- a/js/ext/angular/test/directive/ionicBackdrop.unit.js
+++ b/js/ext/angular/test/directive/ionicBackdrop.unit.js
@@ -1,38 +1,34 @@
describe('$ionicBackdrop service', function() {
beforeEach(module('ionic'));
- beforeEach(function() {
+ beforeEach(inject(function($animate) {
ionic.requestAnimationFrame = function(cb) { cb(); };
- });
-
- it('should create backdrop first time then reuse', inject(function($ionicBackdrop) {
- var el = $ionicBackdrop._getElement();
- var el2 = $ionicBackdrop._getElement();
- expect(el.hasClass('backdrop')).toBe(true);
- expect(el.hasClass('ng-hide')).toBe(true);
- expect(el[0]).toBe(el2[0]);
}));
- it('should remove ngHide on retain', inject(function($ionicBackdrop) {
- var el = $ionicBackdrop._getElement();
+ it('should remove ngHide on retain', inject(function($ionicBackdrop, $timeout) {
+ var el = $ionicBackdrop._element;
expect(el.hasClass('ng-hide')).toBe(true);
$ionicBackdrop.retain();
+ $timeout.flush();
expect(el.hasClass('ng-hide')).toBe(false);
}));
- it('should add ngHide on retain', inject(function($ionicBackdrop) {
- var el = $ionicBackdrop._getElement();
+ it('should add ngHide on retain', inject(function($ionicBackdrop, $timeout) {
+ var el = $ionicBackdrop._element;
expect(el.hasClass('ng-hide')).toBe(true);
$ionicBackdrop.retain();
+ $timeout.flush();
expect(el.hasClass('ng-hide')).toBe(false);
$ionicBackdrop.release();
+ $timeout.flush();
expect(el.hasClass('ng-hide')).toBe(true);
}));
- it('should require equal releases and retains', inject(function($ionicBackdrop) {
- var el = $ionicBackdrop._getElement();
+ it('should require equal releases and retains', inject(function($ionicBackdrop, $timeout) {
+ var el = $ionicBackdrop._element;
expect(el.hasClass('ng-hide')).toBe(true);
$ionicBackdrop.retain();
+ $timeout.flush();
expect(el.hasClass('ng-hide')).toBe(false);
$ionicBackdrop.retain();
expect(el.hasClass('ng-hide')).toBe(false);
@@ -43,6 +39,7 @@ describe('$ionicBackdrop service', function() {
$ionicBackdrop.release();
expect(el.hasClass('ng-hide')).toBe(false);
$ionicBackdrop.release();
+ $timeout.flush();
expect(el.hasClass('ng-hide')).toBe(true);
}));
});
diff --git a/js/ext/angular/test/service/animateClassToggler.unit.js b/js/ext/angular/test/service/animateClassToggler.unit.js
new file mode 100644
index 0000000000..3226432fa0
--- /dev/null
+++ b/js/ext/angular/test/service/animateClassToggler.unit.js
@@ -0,0 +1,66 @@
+describe('$animateClassToggler service', function() {
+ beforeEach(module('ionic'));
+
+ var toggler, el;
+ beforeEach(inject(function($animateClassToggler) {
+ el = angular.element('
');
+ toggler = $animateClassToggler(el, 'foo');
+ ionic.requestAnimationFrame = function(cb) { cb(); };
+ }));
+
+ function flush() {
+ inject(function($timeout) {
+ $timeout.flush();
+ });
+ }
+
+ it('addClass should go to toggle', function() {
+ spyOn(toggler, '_toggle');
+ toggler.addClass();
+ expect(toggler._toggle).toHaveBeenCalledWith(true);
+ });
+ it('removeClass should go to toggle', function() {
+ spyOn(toggler, '_toggle');
+ toggler.removeClass();
+ expect(toggler._toggle).toHaveBeenCalledWith(false);
+ });
+ it('toggle should add class and remove class', function() {
+ toggler._toggle(true);
+ flush();
+ expect(el.hasClass('foo')).toBe(true);
+ toggler._toggle(false);
+ flush();
+ expect(el.hasClass('foo')).toBe(false);
+ });
+
+ it('toggle should set nextOperation if animating', function() {
+ toggler._animating = true;
+ toggler._toggle(true);
+ expect(toggler._nextOperation).toBe('addClass');
+ toggler._toggle(false);
+ expect(toggler._nextOperation).toBe('removeClass');
+ });
+
+ it('animate should set animating to true and animationDone when done', inject(function($rootScope, $animate) {
+ spyOn($rootScope, '$evalAsync').andCallThrough();
+ spyOn($animate, 'addClass');
+ toggler._animate('addClass');
+ expect(toggler._animating).toBe(true);
+ expect($rootScope.$evalAsync).toHaveBeenCalled();
+ flush();
+ expect($animate.addClass).toHaveBeenCalledWith(el, 'foo', toggler._animationDone);
+ }));
+
+ it('animationDone should use nextOperation and unset animating if no more', function() {
+ spyOn(toggler, '_animate').andCallThrough();
+ toggler._animating = true;
+ toggler._nextOperation = 'addClass';
+ toggler._animationDone();
+ expect(toggler._nextOperation).toBeFalsy();
+ expect(toggler._animate).toHaveBeenCalled();
+ flush();
+ expect(el.hasClass('foo')).toBe(true);
+ expect(toggler._animating).toBe(false);
+ });
+
+});
diff --git a/js/ext/angular/test/service/ionicLoading.unit.js b/js/ext/angular/test/service/ionicLoading.unit.js
index f1f6542804..d786130c59 100644
--- a/js/ext/angular/test/service/ionicLoading.unit.js
+++ b/js/ext/angular/test/service/ionicLoading.unit.js
@@ -41,11 +41,12 @@ describe('$ionicLoading service', function() {
expect(loader.durationTimeout).toBeTruthy();
expect(loader.durationTimeout.$$timeoutId).toBeTruthy();
}));
- it('should remove ng-hide after raf', inject(function($ionicLoading) {
+ it('should remove ng-hide', inject(function($ionicLoading, $timeout) {
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
ionic.requestAnimationFrame = function(cb) { cb(); };
expect(loader.element.hasClass('ng-hide')).toBe(true);
loader.show({});
+ $timeout.flush();
expect(loader.element.hasClass('ng-hide')).toBe(false);
}));
it('should isShown = true', inject(function($ionicLoading) {
@@ -137,7 +138,7 @@ describe('$ionicLoading service', function() {
it('show should only removeClass after raf is still isShown', inject(function($ionicLoading) {
var loader = TestUtil.unwrapPromise($ionicLoading._getLoader());
var rafCallback;
- ionic.requestAnimationFrame = function(cb) {
+ ionic.requestAnimationFrame = function(cb) {
rafCallback = cb;
};
loader.show({});
diff --git a/js/utils/dom.js b/js/utils/dom.js
index c8297cd267..91d2b9fd4e 100644
--- a/js/utils/dom.js
+++ b/js/utils/dom.js
@@ -187,6 +187,16 @@
el.style.marginLeft = (-el.offsetWidth) / 2 + 'px';
el.style.marginTop = (-el.offsetHeight) / 2 + 'px';
},
+ //Center twice, after raf, to fix a bug with ios and showing elements
+ //that have just been attached to the DOM.
+ centerElementByMarginTwice: function(el) {
+ ionic.requestAnimationFrame(function() {
+ ionic.DomUtil.centerElementByMargin(el);
+ ionic.requestAnimationFrame(function() {
+ ionic.DomUtil.centerElementByMargin(el);
+ });
+ });
+ },
/**
* @ngdoc method