Skip to content

Commit

Permalink
feat($ionicView.unloaded): emit when view unloaded
Browse files Browse the repository at this point in the history
  • Loading branch information
adamdbradley committed Dec 12, 2014
1 parent 6eb7eb1 commit d6bff56
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 87 deletions.
72 changes: 50 additions & 22 deletions js/angular/directive/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,28 +34,56 @@
* </ion-nav-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.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.
*
* <table class="table">
* <tr>
* <td><code>$ionicView.loaded</code></td>
* <td>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.</td>
* </tr>
* <tr>
* <td><code>$ionicView.enter</code></td>
* <td>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.</td>
* </tr>
* <tr>
* <td><code>$ionicView.leave</code></td>
* <td>The view has finished leaving and is no longer the
* active view. This event will fire, whether it is cached or destroyed.</td>
* </tr>
* <tr>
* <td><code>$ionicView.beforeEnter</code></td>
* <td>The view is about to enter and become the active view.</td>
* </tr>
* <tr>
* <td><code>$ionicView.beforeLeave</code></td>
* <td>The view is about to leave and no longer be the active view.</td>
* </tr>
* <tr>
* <td><code>$ionicView.afterEnter</code></td>
* <td>The view has fully entered and is now the active view.</td>
* </tr>
* <tr>
* <td><code>$ionicView.afterLeave</code></td>
* <td>The view has finished leaving and is no longer the active view.</td>
* </tr>
* <tr>
* <td><code>$ionicView.unloaded</code></td>
* <td>The view's controller has been destroyed and its element has been
* removed from the DOM.</td>
* </tr>
* </table>
*
* ## Caching
*
Expand Down
135 changes: 73 additions & 62 deletions js/angular/service/viewSwitcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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) {
Expand Down Expand Up @@ -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' ||
Expand Down Expand Up @@ -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();
}
}

}]);
3 changes: 0 additions & 3 deletions scss/_transitions.scss
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,6 @@ $ios-transition-container-bg-color: #000 !default;
}
}

.back-button {
@include transition-duration(0);
}
}


Expand Down
24 changes: 24 additions & 0 deletions test/unit/angular/directive/navView.unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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('<div><ion-nav-view></ion-nav-view></div>')(scope));

var unloaded;
scope.$on('$ionicView.unloaded', function(ev, d){
unloaded = d;
});

$ionicConfig.views.maxCache(2);

$state.go(page1State);
Expand All @@ -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) {
Expand Down Expand Up @@ -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('<div><ion-nav-view></ion-nav-view></div>')(scope));

var clearCacheCollection = [];
scope.$on('$ionicView.unloaded', function(ev, d){
clearCacheCollection.push(d);
});

$state.go(page1State);
$q.flush();
$timeout.flush();
Expand All @@ -894,15 +910,22 @@ 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);

expect(divs.eq(0).attr('nav-view')).toBe('active');
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);
Expand All @@ -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) {
Expand Down

0 comments on commit d6bff56

Please sign in to comment.