diff --git a/js/angular/controller/navViewController.js b/js/angular/controller/navViewController.js index c83da4c7164..562085c3d06 100644 --- a/js/angular/controller/navViewController.js +++ b/js/angular/controller/navViewController.js @@ -29,6 +29,7 @@ function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate, var transitionDuration, transitionTiming; self.scope = $scope; + self.element = $element; self.init = function() { var navViewName = $attrs.name || ''; diff --git a/js/angular/service/viewSwitcher.js b/js/angular/service/viewSwitcher.js index 2af4698a032..11320309a7c 100644 --- a/js/angular/service/viewSwitcher.js +++ b/js/angular/service/viewSwitcher.js @@ -170,7 +170,7 @@ function($timeout, $document, $q, $ionicClickBlock, $ionicConfig, $ionicNavBarDe if (viewTransition.shouldAnimate) { // attach transitionend events (and fallback timer) - enteringEle.on(TRANSITIONEND_EVENT, transitionComplete); + enteringEle.on(TRANSITIONEND_EVENT, completeOnTransitionEnd); enteringEle.data(DATA_FALLBACK_TIMER, $timeout(transitionComplete, defaultTimeout)); $ionicClickBlock.show(defaultTimeout); } @@ -209,7 +209,7 @@ function($timeout, $document, $q, $ionicClickBlock, $ionicConfig, $ionicNavBarDe run: viewTransition.run, cancel: function(shouldAnimate) { if (shouldAnimate) { - enteringEle.on(TRANSITIONEND_EVENT, cancelTransition); + enteringEle.on(TRANSITIONEND_EVENT, cancelOnTransitionEnd); enteringEle.data(DATA_FALLBACK_TIMER, $timeout(cancelTransition, defaultTimeout)); $ionicClickBlock.show(defaultTimeout); } else { @@ -248,11 +248,17 @@ function($timeout, $document, $q, $ionicClickBlock, $ionicConfig, $ionicNavBarDe } } + // Make sure that transitionend events bubbling up from children won't fire + // transitionComplete. Will only go forward if ev.target == the element listening. + function completeOnTransitionEnd(ev) { + if (ev.target !== this) return; + transitionComplete(); + } function transitionComplete() { if (transitionComplete.x) return; transitionComplete.x = true; - enteringEle.off(TRANSITIONEND_EVENT, transitionComplete); + enteringEle.off(TRANSITIONEND_EVENT, completeOnTransitionEnd); $timeout.cancel(enteringEle.data(DATA_FALLBACK_TIMER)); leavingEle && $timeout.cancel(leavingEle.data(DATA_FALLBACK_TIMER)); @@ -279,10 +285,16 @@ function($timeout, $document, $q, $ionicClickBlock, $ionicConfig, $ionicNavBarDe nextTransition = nextDirection = enteringView = leavingView = enteringEle = leavingEle = null; } + // Make sure that transitionend events bubbling up from children won't fire + // transitionComplete. Will only go forward if ev.target == the element listening. + function cancelOnTransitionEnd(ev) { + if (ev.target !== this) return; + cancelTransition(); + } function cancelTransition() { navViewAttr(enteringEle, VIEW_STATUS_CACHED); navViewAttr(leavingEle, VIEW_STATUS_ACTIVE); - enteringEle.off(TRANSITIONEND_EVENT, cancelTransition); + enteringEle.off(TRANSITIONEND_EVENT, cancelOnTransitionEnd); $timeout.cancel(enteringEle.data(DATA_FALLBACK_TIMER)); ionicViewSwitcher.transitionEnd([navViewCtrl]); } diff --git a/js/utils/dom.js b/js/utils/dom.js index 81bdb71e4a4..17d08e2585f 100644 --- a/js/utils/dom.js +++ b/js/utils/dom.js @@ -297,6 +297,7 @@ } } } + }; //Shortcuts diff --git a/test/unit/angular/service/viewSwitcher.unit.js b/test/unit/angular/service/viewSwitcher.unit.js index 4e96b1296c7..9c241abce08 100644 --- a/test/unit/angular/service/viewSwitcher.unit.js +++ b/test/unit/angular/service/viewSwitcher.unit.js @@ -225,4 +225,58 @@ describe('Ionic View Switcher', function() { expect(switcher.enteringEle().data('$accessed')).toBeDefined(); })); + it('should end the transition on transitionend', inject(function($ionicViewSwitcher, $rootScope, $ionicConfig, $compile) { + $ionicConfig.views.transition('test'); + $ionicConfig.transitions.views.test = function() { + return { run: angular.noop, shouldAnimate: true }; + }; + var afterEnterSpy = jasmine.createSpy('afterEnter'); + var navViewCtrl = setup(); + var enteringEle = $compile('')($rootScope); + navViewCtrl.element.append(enteringEle); + + enteringEle.data('$eleId', 'foo'); + var switcher = $ionicViewSwitcher.create(navViewCtrl, {}, { + viewId: 'foo' + }); + + switcher.loadViewElements({}); + $rootScope.$on('$ionicView.afterEnter', afterEnterSpy); + switcher.transition('forward'); + + expect(afterEnterSpy).not.toHaveBeenCalled(); + enteringEle.triggerHandler('transitionend'); + expect(afterEnterSpy).toHaveBeenCalled(); + })); + + it('should not end the transition on bubbled transitionend', inject(function($ionicViewSwitcher, $rootScope, $ionicConfig, $compile) { + $ionicConfig.views.transition('test'); + $ionicConfig.transitions.views.test = function() { + return { run: angular.noop, shouldAnimate: true }; + }; + var afterEnterSpy = jasmine.createSpy('afterEnter'); + var navViewCtrl = setup(); + var enteringEle = $compile('')($rootScope); + navViewCtrl.element.append(enteringEle); + + var enteringEleChild = angular.element(''); + enteringEle.append(enteringEleChild); + + enteringEle.data('$eleId', 'foo'); + var switcher = $ionicViewSwitcher.create(navViewCtrl, {}, { + viewId: 'foo' + }); + + switcher.loadViewElements({}); + $rootScope.$on('$ionicView.afterEnter', afterEnterSpy); + switcher.transition('forward'); + + expect(afterEnterSpy).not.toHaveBeenCalled(); + enteringEle.triggerHandler({ + type: 'transitionend', + target: enteringEleChild[0] + }); + expect(afterEnterSpy).not.toHaveBeenCalled(); + })); + });