From d6bff56445ca23f244f2d1374bf275ac23ff8799 Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Fri, 12 Dec 2014 16:27:40 -0600 Subject: [PATCH] feat($ionicView.unloaded): emit when view unloaded --- js/angular/directive/view.js | 72 +++++++---- js/angular/service/viewSwitcher.js | 135 +++++++++++--------- scss/_transitions.scss | 3 - test/unit/angular/directive/navView.unit.js | 24 ++++ 4 files changed, 147 insertions(+), 87 deletions(-) diff --git a/js/angular/directive/view.js b/js/angular/directive/view.js index 227d92903b..f4a8004da8 100644 --- a/js/angular/directive/view.js +++ b/js/angular/directive/view.js @@ -34,28 +34,56 @@ * * ``` * - ## View LifeCycle and Events - - Views can be cached, which means *controllers normally only load once*, which may - affect your controller logic. To know when a view has entered or left, events - have been added that are emitted from the view's scope. These events also - contain data about the view, such as the title and whether the back button should - show. Also contained is transition data, such as the transition type and - direction that will be or was used. - - * `$ionicView.loaded`: The view has loaded. This event only happens once per - view being created and added to the DOM. If a view leaves but is cached, - then this event will not fire again on a subsequent viewing. The loaded event - is good place to put your setup code for the view; however, it is not the - recommended event to listen to when a view becomes active. - * `$ionicView.enter`: The view has fully entered and is now the active view. - This event will fire, whether it was the first load or a cached view. - * `$ionicView.leave`: The view has finished leaving and is no longer the - active view. This event will fire, whether it is cached or destroyed. - * `$ionicView.beforeEnter`: The view is about to enter and become the active view. - * `$ionicView.beforeLeave`: The view is about to leave and no longer be the active view. - * `$ionicView.afterEnter`: The view has fully entered and is now the active view. - * `$ionicView.afterLeave`: The view has finished leaving and is no longer the active view. + * ## View LifeCycle and Events + * + * Views can be cached, which means *controllers normally only load once*, which may + * affect your controller logic. To know when a view has entered or left, events + * have been added that are emitted from the view's scope. These events also + * contain data about the view, such as the title and whether the back button should + * show. Also contained is transition data, such as the transition type and + * direction that will be or was used. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
$ionicView.loadedThe view has loaded. This event only happens once per + * view being created and added to the DOM. If a view leaves but is cached, + * then this event will not fire again on a subsequent viewing. The loaded event + * is good place to put your setup code for the view; however, it is not the + * recommended event to listen to when a view becomes active.
$ionicView.enterThe view has fully entered and is now the active view. + * This event will fire, whether it was the first load or a cached view.
$ionicView.leaveThe view has finished leaving and is no longer the + * active view. This event will fire, whether it is cached or destroyed.
$ionicView.beforeEnterThe view is about to enter and become the active view.
$ionicView.beforeLeaveThe view is about to leave and no longer be the active view.
$ionicView.afterEnterThe view has fully entered and is now the active view.
$ionicView.afterLeaveThe view has finished leaving and is no longer the active view.
$ionicView.unloadedThe view's controller has been destroyed and its element has been + * removed from the DOM.
* * ## Caching * diff --git a/js/angular/service/viewSwitcher.js b/js/angular/service/viewSwitcher.js index 197fb63950..9ab2c45a7d 100644 --- a/js/angular/service/viewSwitcher.js +++ b/js/angular/service/viewSwitcher.js @@ -19,6 +19,7 @@ function($timeout, $document, $q, $ionicClickBlock, $ionicConfig, $ionicNavBarDe var DATA_ELE_IDENTIFIER = '$eleId'; var DATA_VIEW_ACCESSED = '$accessed'; var DATA_FALLBACK_TIMER = '$fallbackTimer'; + var DATA_VIEW = '$viewData'; var NAV_VIEW_ATTR = 'nav-view'; var HISTORY_CURSOR_ATTR = 'history-cursor'; var VIEW_STATUS_ACTIVE = 'active'; @@ -33,68 +34,6 @@ function($timeout, $document, $q, $ionicClickBlock, $ionicConfig, $ionicNavBarDe var cachedAttr = ionic.DomUtil.cachedAttr; var transitionPromises = []; - function getViewElementIdentifier(locals, view) { - if (viewState(locals).abstract) return viewState(locals).name; - if (view) return view.stateId || view.viewId; - return ionic.Utils.nextUid(); - } - - function viewState(locals) { - return locals && locals.$$state && locals.$$state.self || {}; - } - - function getTransitionData(viewLocals, enteringEle, direction, view) { - // Priority - // 1) attribute directive on the button/link to this view - // 2) entering element's attribute - // 3) entering view's $state config property - // 4) view registration data - // 5) global config - // 6) fallback value - - var state = viewState(viewLocals); - var viewTransition = nextTransition || cachedAttr(enteringEle, 'view-transition') || state.viewTransition || $ionicConfig.views.transition() || 'ios'; - var navBarTransition = $ionicConfig.navBar.transition(); - direction = nextDirection || cachedAttr(enteringEle, 'view-direction') || state.viewDirection || direction || 'none'; - - return extend(getViewData(view), { - transition: viewTransition, - navBarTransition: navBarTransition === 'view' ? viewTransition : navBarTransition, - direction: direction, - shouldAnimate: (viewTransition !== 'none' && direction !== 'none') - }); - } - - function getViewData(view) { - view = view || {}; - return { - viewId: view.viewId, - historyId: view.historyId, - stateId: view.stateId, - stateName: view.stateName, - stateParams: view.stateParams - }; - } - - function navViewAttr(ele, value) { - if (arguments.length > 1) { - cachedAttr(ele, NAV_VIEW_ATTR, value); - } else { - return cachedAttr(ele, NAV_VIEW_ATTR); - } - } - - function destroyViewEle(ele) { - // we found an element that should be removed - // destroy its scope, then remove the element - if (ele && ele.length) { - var viewScope = ele.scope(); - viewScope && viewScope.$destroy(); - ele.remove(); - } - } - - var ionicViewSwitcher = { create: function(navViewCtrl, viewLocals, enteringView, leavingView) { @@ -176,6 +115,13 @@ function($timeout, $document, $q, $ionicClickBlock, $ionicConfig, $ionicNavBarDe var transitionFn = $ionicConfig.transitions.views[enteringData.transition] || $ionicConfig.transitions.views.none; transitionFn(enteringEle, null, enteringData.direction, true).run(0); + enteringEle.data(DATA_VIEW, { + viewId: enteringData.viewId, + historyId: enteringData.historyId, + stateName: enteringData.stateName, + stateParams: enteringData.stateParams + }); + // if the current state has cache:false // or the element has cache-view="false" attribute if (viewState(viewLocals).cache === false || viewState(viewLocals).cache === 'false' || @@ -404,4 +350,69 @@ function($timeout, $document, $q, $ionicClickBlock, $ionicConfig, $ionicNavBarDe return ionicViewSwitcher; + + function getViewElementIdentifier(locals, view) { + if (viewState(locals).abstract) return viewState(locals).name; + if (view) return view.stateId || view.viewId; + return ionic.Utils.nextUid(); + } + + function viewState(locals) { + return locals && locals.$$state && locals.$$state.self || {}; + } + + function getTransitionData(viewLocals, enteringEle, direction, view) { + // Priority + // 1) attribute directive on the button/link to this view + // 2) entering element's attribute + // 3) entering view's $state config property + // 4) view registration data + // 5) global config + // 6) fallback value + + var state = viewState(viewLocals); + var viewTransition = nextTransition || cachedAttr(enteringEle, 'view-transition') || state.viewTransition || $ionicConfig.views.transition() || 'ios'; + var navBarTransition = $ionicConfig.navBar.transition(); + direction = nextDirection || cachedAttr(enteringEle, 'view-direction') || state.viewDirection || direction || 'none'; + + return extend(getViewData(view), { + transition: viewTransition, + navBarTransition: navBarTransition === 'view' ? viewTransition : navBarTransition, + direction: direction, + shouldAnimate: (viewTransition !== 'none' && direction !== 'none') + }); + } + + function getViewData(view) { + view = view || {}; + return { + viewId: view.viewId, + historyId: view.historyId, + stateId: view.stateId, + stateName: view.stateName, + stateParams: view.stateParams + }; + } + + function navViewAttr(ele, value) { + if (arguments.length > 1) { + cachedAttr(ele, NAV_VIEW_ATTR, value); + } else { + return cachedAttr(ele, NAV_VIEW_ATTR); + } + } + + function destroyViewEle(ele) { + // we found an element that should be removed + // destroy its scope, then remove the element + if (ele && ele.length) { + var viewScope = ele.scope(); + if (viewScope) { + viewScope.$emit('$ionicView.unloaded', ele.data(DATA_VIEW)); + viewScope.$destroy(); + } + ele.remove(); + } + } + }]); diff --git a/scss/_transitions.scss b/scss/_transitions.scss index 91b1012a9e..dea82a5678 100644 --- a/scss/_transitions.scss +++ b/scss/_transitions.scss @@ -68,9 +68,6 @@ $ios-transition-container-bg-color: #000 !default; } } - .back-button { - @include transition-duration(0); - } } diff --git a/test/unit/angular/directive/navView.unit.js b/test/unit/angular/directive/navView.unit.js index 1666aa44df..5dab25e0cc 100644 --- a/test/unit/angular/directive/navView.unit.js +++ b/test/unit/angular/directive/navView.unit.js @@ -565,6 +565,11 @@ describe('Ionic nav-view', function() { it('should not cache ion-nav-views that were forward when moving back', inject(function ($state, $q, $timeout, $compile, $ionicConfig) { elem.append($compile('
')(scope)); + var unloaded; + scope.$on('$ionicView.unloaded', function(ev, d){ + unloaded = d; + }); + $ionicConfig.views.maxCache(2); $state.go(page1State); @@ -581,26 +586,32 @@ describe('Ionic nav-view', function() { $q.flush(); $timeout.flush(); expect(elem.find('ion-nav-view').find('div').length).toBe(3); + expect(unloaded).toBeUndefined(); $state.go(page4State); $q.flush(); $timeout.flush(); expect(elem.find('ion-nav-view').find('div').length).toBe(3); + expect(unloaded.historyId).toBe('root'); + expect(unloaded.stateName).toBe('page1'); $state.go(page3State); $q.flush(); $timeout.flush(); expect(elem.find('ion-nav-view').find('div').length).toBe(2); + expect(unloaded.stateName).toBe('page4'); $state.go(page2State); $q.flush(); $timeout.flush(); expect(elem.find('ion-nav-view').find('div').length).toBe(1); + expect(unloaded.stateName).toBe('page3'); $state.go(page1State); $q.flush(); $timeout.flush(); expect(elem.find('ion-nav-view').find('div').length).toBe(1); + expect(unloaded.stateName).toBe('page2'); })); it('should cache ion-nav-views that were forward when moving back with $ionicConfig.cacheForwardViews=true', inject(function ($state, $q, $timeout, $compile, $ionicConfig) { @@ -870,6 +881,11 @@ describe('Ionic nav-view', function() { it('should clear ion-nav-view cache', inject(function ($state, $q, $timeout, $compile, $ionicHistory) { elem.append($compile('
')(scope)); + var clearCacheCollection = []; + scope.$on('$ionicView.unloaded', function(ev, d){ + clearCacheCollection.push(d); + }); + $state.go(page1State); $q.flush(); $timeout.flush(); @@ -894,8 +910,14 @@ describe('Ionic nav-view', function() { expect(divs.eq(2).attr('nav-view')).toBe('active'); expect(divs.eq(2).text()).toBe('page3'); + expect(clearCacheCollection.length).toBe(0); $ionicHistory.clearCache(); + expect(clearCacheCollection.length).toBe(2); + expect(clearCacheCollection[0].stateName).toBe('page1'); + expect(clearCacheCollection[1].stateName).toBe('page2'); + clearCacheCollection = []; + var divs = elem.find('ion-nav-view').find('div'); expect(divs.length).toBe(1); @@ -903,6 +925,7 @@ describe('Ionic nav-view', function() { expect(divs.eq(0).text()).toBe('page3'); $ionicHistory.clearCache(); + expect(clearCacheCollection.length).toBe(0); var divs = elem.find('ion-nav-view').find('div'); expect(divs.length).toBe(1); @@ -919,6 +942,7 @@ describe('Ionic nav-view', function() { expect(divs.eq(0).attr('nav-view')).toBe('active'); expect(divs.eq(0).text()).toBe('page2'); + expect(clearCacheCollection[0].stateName).toBe('page3'); })); it('should create and cache tabs', inject(function ($state, $q, $timeout, $compile, $ionicConfig) {