From 5ab49b5df8c124a27a34ed96daf4186824b78091 Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Mon, 17 Nov 2014 13:20:14 -0600 Subject: [PATCH] refactor(viewEvents): create loaded, enter, leave MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add to the possible events emitted from the view’s scope, $ionicView.loaded, $ionicView.enter, $ionicView.leave, in addition to the existing events. --- js/angular/controller/navViewController.js | 8 +- js/angular/service/viewSwitcher.js | 60 ++++---- test/unit/angular/directive/navView.unit.js | 134 ++++++++++++++++++ .../unit/angular/service/viewSwitcher.unit.js | 6 +- 4 files changed, 178 insertions(+), 30 deletions(-) diff --git a/js/angular/controller/navViewController.js b/js/angular/controller/navViewController.js index 18c8b26a7f..bb393da0d9 100644 --- a/js/angular/controller/navViewController.js +++ b/js/angular/controller/navViewController.js @@ -32,6 +32,8 @@ function($scope, $element, $attrs, $ionicNavBarDelegate, $ionicHistory, $ionicVi self.register = function(viewLocals) { + var leavingView = extend({}, $ionicHistory.currentView()); + // register that a view is coming in and get info on how it should transition var registerData = $ionicHistory.register($scope, viewLocals); @@ -39,7 +41,7 @@ function($scope, $element, $attrs, $ionicNavBarDelegate, $ionicHistory, $ionicVi self.update(registerData); // begin rendering and transitioning - self.render(registerData, viewLocals); + self.render(registerData, viewLocals, leavingView); }; @@ -75,12 +77,12 @@ function($scope, $element, $attrs, $ionicNavBarDelegate, $ionicHistory, $ionicVi }; - self.render = function(registerData, viewLocals) { + self.render = function(registerData, viewLocals, leavingView) { var enteringView = $ionicHistory.getViewById(registerData.viewId) || {}; // register the view and figure out where it lives in the various // histories and nav stacks, along with how views should enter/leave - var switcher = $ionicViewSwitcher.create($scope, $element, viewLocals, enteringView); + var switcher = $ionicViewSwitcher.create($scope, $element, viewLocals, enteringView, leavingView); // init the rendering of views for this navView directive switcher.init(registerData, function() { diff --git a/js/angular/service/viewSwitcher.js b/js/angular/service/viewSwitcher.js index 483bffbe7d..b83e24fbb1 100644 --- a/js/angular/service/viewSwitcher.js +++ b/js/angular/service/viewSwitcher.js @@ -46,7 +46,7 @@ function($timeout, $compile, $controller, $document, $ionicClickBlock, $ionicCon return locals && locals.$$state && locals.$$state.self || {}; } - function getTransitionData(viewLocals, enteringEle, direction, enteringView, showBack) { + function getTransitionData(viewLocals, enteringEle, direction, showBack, view) { // Priority // 1) attribute directive on the button/link to this view // 2) entering element's attribute @@ -56,21 +56,24 @@ function($timeout, $compile, $controller, $document, $ionicClickBlock, $ionicCon // 6) fallback value var state = viewState(viewLocals); - enteringView = enteringView || {}; - var transition = nextTransition || cachedAttr(enteringEle, 'view-transition') || state.viewTransition || $ionicConfig.views.transition() || 'ios'; direction = nextDirection || cachedAttr(enteringEle, 'view-direction') || state.viewDirection || direction || 'none'; - var shouldAnimate = (transition !== 'none' && direction !== 'none'); - return { + return extend(getViewData(view), { transition: transition, direction: direction, - shouldAnimate: shouldAnimate, - viewId: enteringView.viewId, - stateId: enteringView.stateId, - stateName: enteringView.stateName, - stateParams: enteringView.stateParams, + shouldAnimate: (transition !== 'none' && direction !== 'none'), showBack: !!showBack + }) + } + + function getViewData(view) { + view = view || {}; + return { + viewId: view.viewId, + stateId: view.stateId, + stateName: view.stateName, + stateParams: view.stateParams }; } @@ -95,7 +98,7 @@ function($timeout, $compile, $controller, $document, $ionicClickBlock, $ionicCon var ionicViewSwitcher = { - create: function(navViewScope, navViewElement, viewLocals, enteringView) { + create: function(navViewScope, navViewElement, viewLocals, enteringView, leavingView) { // get a reference to an entering/leaving element if they exist // loop through to see if the view is already in the navViewElement var enteringEle, leavingEle; @@ -194,6 +197,8 @@ function($timeout, $compile, $controller, $document, $ionicClickBlock, $ionicCon // run link with the view's scope link(scope); + + scope.$emit('$ionicView.loaded', enteringView); } // update that this view was just accessed @@ -203,20 +208,21 @@ function($timeout, $compile, $controller, $document, $ionicClickBlock, $ionicCon }, transition: function(direction, showBack) { - var transData = getTransitionData(viewLocals, enteringEle, direction, enteringView, showBack); - transData.transitionId = transitionId; + var enteringData = getTransitionData(viewLocals, enteringEle, direction, showBack, enteringView); + var leavingData = extend(extend({}, enteringData), getViewData(leavingView)); + enteringData.transitionId = leavingData.transitionId = transitionId; - cachedAttr(enteringEle.parent(), 'nav-view-transition', transData.transition); - cachedAttr(enteringEle.parent(), 'nav-view-direction', transData.direction); + cachedAttr(enteringEle.parent(), 'nav-view-transition', enteringData.transition); + cachedAttr(enteringEle.parent(), 'nav-view-direction', enteringData.direction); // cancel any previous transition complete fallbacks $timeout.cancel(enteringEle.data(DATA_FALLBACK_TIMER)); - switcher.emit('before', transData); + switcher.emit('before', enteringData, leavingData); // 1) get the transition ready and see if it'll animate var transitionFn = $ionicConfig.transitions.views[$ionicConfig.views.transition()]; - var viewTransition = transitionFn(enteringEle, leavingEle, direction, transData.shouldAnimate); + var viewTransition = transitionFn(enteringEle, leavingEle, direction, enteringData.shouldAnimate); if (viewTransition.shouldAnimate) { // 2) attach transitionend events (and fallback timer) @@ -228,7 +234,7 @@ function($timeout, $compile, $controller, $document, $ionicClickBlock, $ionicCon // 3) stage entering element, opacity 0, no transition duration navViewAttr(enteringEle, VIEW_STATUS_STAGED); - if (transData.direction == 'swap') { + if (enteringData.direction == 'swap') { historyCursorAttr(enteringEle, HISTORY_ROOT); } @@ -264,13 +270,13 @@ function($timeout, $compile, $controller, $document, $ionicClickBlock, $ionicCon leavingEle && leavingEle.off(TRANSITIONEND_EVENT, transitionComplete); $timeout.cancel(enteringEle.data(DATA_FALLBACK_TIMER)); - switcher.emit('after', transData); + switcher.emit('after', enteringData, leavingData); // 8) fire off that the entire transition has completed // only the most recent transition should do cleanup if (transitionId === transitionCounter) { ionicViewSwitcher.setActiveView(navViewElement); - switcher.cleanup(transData); + switcher.cleanup(enteringData); ionicViewSwitcher.isTransitioning(false); $ionicClickBlock.hide(); } @@ -280,21 +286,27 @@ function($timeout, $compile, $controller, $document, $ionicClickBlock, $ionicCon } // remove any references that could cause memory issues - nextTransition = nextDirection = enteringView = enteringEle = leavingEle = null; + nextTransition = nextDirection = enteringView = leavingView = enteringEle = leavingEle = null; } }, - emit: function(step, transData) { + emit: function(step, enteringData, leavingData) { var scope = enteringEle.scope(); if (scope) { - scope.$emit('$ionicView.' + step + 'Enter', transData); + scope.$emit('$ionicView.' + step + 'Enter', enteringData); + if (step == 'after') { + scope.$emit('$ionicView.enter', enteringData); + } } if (leavingEle) { scope = leavingEle.scope(); if (scope) { - scope.$emit('$ionicView.' + step + 'Leave', transData); + scope.$emit('$ionicView.' + step + 'Leave', leavingData); + if (step == 'after') { + scope.$emit('$ionicView.leave', leavingData); + } } } }, diff --git a/test/unit/angular/directive/navView.unit.js b/test/unit/angular/directive/navView.unit.js index 3c4d4ce56d..9cf32a3a52 100644 --- a/test/unit/angular/directive/navView.unit.js +++ b/test/unit/angular/directive/navView.unit.js @@ -679,6 +679,140 @@ describe('Ionic nav-view', function() { expect(divs.eq(3).text()).toBe('page5'); })); + it('should emit $ionicView loaded event only once if cached', inject(function ($state, $q, $timeout, $compile) { + var loaded; + scope.$on('$ionicView.loaded', function(ev, d){ + loaded = d; + }); + + elem.append($compile('
')(scope)); + + $state.go(page1State); + $q.flush(); + $timeout.flush(); + $timeout.flush(); + + expect(loaded.stateName).toEqual('page1'); + loaded = null; + + $state.go(page2State); + $q.flush(); + $timeout.flush(); + $timeout.flush(); + expect(loaded.stateName).toEqual('page2'); + loaded = null; + + $state.go(page1State); + $q.flush(); + $timeout.flush(); + $timeout.flush(); + + expect(loaded).toEqual(null); + + $state.go(page2State); + $q.flush(); + $timeout.flush(); + $timeout.flush(); + + expect(loaded.stateName).toEqual('page2'); + loaded = null; + })); + + it('should emit $ionicView enter events', inject(function ($state, $q, $timeout, $compile) { + var beforeEnter, afterEnter, enter; + scope.$on('$ionicView.beforeEnter', function(ev, d){ + beforeEnter = d; + }); + scope.$on('$ionicView.afterEnter', function(ev, d){ + afterEnter = d; + }); + scope.$on('$ionicView.enter', function(ev, d){ + enter = d; + }); + + elem.append($compile('
')(scope)); + + $state.go(page1State); + $q.flush(); + $timeout.flush(); + $timeout.flush(); + + expect(beforeEnter.stateName).toEqual('page1'); + expect(afterEnter.stateName).toEqual('page1'); + expect(enter.stateName).toEqual('page1'); + expect(enter.transitionId).toEqual(1); + + $state.go(page2State); + $q.flush(); + $timeout.flush(); + $timeout.flush(); + + expect(beforeEnter.stateName).toEqual('page2'); + expect(afterEnter.stateName).toEqual('page2'); + expect(enter.stateName).toEqual('page2'); + expect(enter.transitionId).toEqual(2); + + $state.go(page1State); + $q.flush(); + $timeout.flush(); + $timeout.flush(); + + expect(beforeEnter.stateName).toEqual('page1'); + expect(afterEnter.stateName).toEqual('page1'); + expect(enter.stateName).toEqual('page1'); + expect(enter.transitionId).toEqual(3); + })); + + it('should emit $ionicView leave events', inject(function ($state, $q, $timeout, $compile) { + var beforeLeave, afterLeave, leave; + scope.$on('$ionicView.beforeLeave', function(ev, d){ + beforeLeave = d; + }); + scope.$on('$ionicView.afterLeave', function(ev, d){ + afterLeave = d; + }); + scope.$on('$ionicView.leave', function(ev, d){ + leave = d; + }); + + elem.append($compile('
')(scope)); + + $state.go(page1State); + $q.flush(); + $timeout.flush(); + $timeout.flush(); + + $state.go(page2State); + $q.flush(); + $timeout.flush(); + $timeout.flush(); + + expect(beforeLeave.stateName).toEqual('page1'); + expect(afterLeave.stateName).toEqual('page1'); + expect(leave.stateName).toEqual('page1'); + expect(leave.transitionId).toEqual(2); + + $state.go(page1State); + $q.flush(); + $timeout.flush(); + $timeout.flush(); + + expect(beforeLeave.stateName).toEqual('page2'); + expect(afterLeave.stateName).toEqual('page2'); + expect(leave.stateName).toEqual('page2'); + expect(leave.transitionId).toEqual(3); + + $state.go(page2State); + $q.flush(); + $timeout.flush(); + $timeout.flush(); + + expect(beforeLeave.stateName).toEqual('page1'); + expect(afterLeave.stateName).toEqual('page1'); + expect(leave.stateName).toEqual('page1'); + expect(leave.transitionId).toEqual(4); + })); + }); angular.module('ngMock').config(function ($provide) { diff --git a/test/unit/angular/service/viewSwitcher.unit.js b/test/unit/angular/service/viewSwitcher.unit.js index 4f84b284d3..4082e45493 100644 --- a/test/unit/angular/service/viewSwitcher.unit.js +++ b/test/unit/angular/service/viewSwitcher.unit.js @@ -80,13 +80,13 @@ describe('Ionic View Switcher', function() { })); it('should set showBack when the view data sets it', inject(function($ionicViewSwitcher) { - var d = $ionicViewSwitcher.getTransitionData(null, null, null, null, true); + var d = $ionicViewSwitcher.getTransitionData(null, null, null, true); expect(d.showBack).toEqual(true); - d = $ionicViewSwitcher.getTransitionData(null, null, null, null, false); + d = $ionicViewSwitcher.getTransitionData(null, null, null, false); expect(d.showBack).toEqual(false); - d = $ionicViewSwitcher.getTransitionData(null, null, null, null, null); + d = $ionicViewSwitcher.getTransitionData(null, null, null, null); expect(d.showBack).toEqual(false); }));