Skip to content

Commit 465ea76

Browse files
committed
fix(navBar): animations, hide back button, no flicker
Closes #653
1 parent 53aeacc commit 465ea76

File tree

2 files changed

+79
-27
lines changed

2 files changed

+79
-27
lines changed

js/ext/angular/src/directive/ionicViewState.js

+73-21
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ angular.module('ionic.ui.viewState', ['ionic.service.view', 'ionic.service.gestu
2323
* Our Nav Bar directive which updates as the controller state changes.
2424
*/
2525
.directive('ionNavBar', ['$ionicViewService', '$rootScope', '$animate', '$compile',
26-
function( $ionicViewService, $rootScope, $animate, $compile) {
26+
function( $ionicViewService, $rootScope, $animate, $compile) {
2727

2828
return {
2929
restrict: 'E',
@@ -38,36 +38,41 @@ angular.module('ionic.ui.viewState', ['ionic.service.view', 'ionic.service.gestu
3838
},
3939
controller: function() {},
4040
template:
41-
'<header class="bar bar-header nav-bar {{type}} {{isReverse ? \'reverse\' : \'\'}} ' +
42-
'{{isInvisible ? \'invisible\' : \'\'}} {{animateEnabled ? animation : \'\'}}">' +
43-
'<ion-nav-back-button ng-if="backButtonEnabled && (backType || backLabel || backIcon)" ' +
44-
'type="backType" label="backLabel" icon="backIcon" class="invisible" ion-async-visible>' +
41+
'<header class="bar bar-header nav-bar{{navBarClass()}}">' +
42+
'<ion-nav-back-button ng-if="(backType || backLabel || backIcon)" ' +
43+
'type="backType" label="backLabel" icon="backIcon" class="hide" ' +
44+
'ng-class="{hide: !backButtonEnabled}">' +
4545
'</ion-nav-back-button>' +
4646
'<div class="buttons left-buttons"> ' +
4747
'<button ng-click="button.tap($event)" ng-repeat="button in leftButtons" ' +
4848
'class="button no-animation {{button.type}}" ng-bind-html="button.content">' +
4949
'</button>' +
5050
'</div>' +
5151

52-
//ng-repeat makes it easy to add new / remove old and have proper enter/leave anims
53-
'<h1 ng-repeat="title in titles" ng-bind-html="title" class="title invisible" ion-async-visible ion-nav-bar-title></h1>' +
52+
'<h1 ng-bind-html="title" class="title"></h1>' +
5453

55-
'<div class="buttons right-buttons" ng-if="rightButtons.length"> ' +
56-
'<button ng-click="button.tap($event)" ng-repeat="button in rightButtons" '+
57-
'class="button no-animation {{button.type}}" ng-bind-html="button.content">' +
54+
'<div class="buttons right-buttons"> ' +
55+
'<button ng-click="button.tap($event)" ng-repeat="button in rightButtons" '+
56+
'class="button no-animation {{button.type}}" ng-bind-html="button.content">' +
5857
'</button>' +
5958
'</div>' +
6059
'</header>',
6160
compile: function(tElement, tAttrs) {
6261

6362
return function link($scope, $element, $attr) {
64-
$scope.titles = [];
6563
//defaults
66-
$scope.backButtonEnabled = true;
64+
$scope.backButtonEnabled = false;
6765
$scope.animateEnabled = true;
6866
$scope.isReverse = false;
6967
$scope.isInvisible = true;
7068

69+
$scope.navBarClass = function() {
70+
return ($scope.type ? ' ' + $scope.type : '') +
71+
($scope.isReverse ? ' reverse' : '') +
72+
($scope.isInvisible ? ' invisible' : '') +
73+
(!$scope.animationDisabled && $scope.animation ? ' ' + $scope.animation : '');
74+
};
75+
7176
// Initialize our header bar view which will handle
7277
// resizing and aligning our title labels
7378
var hb = new ionic.views.HeaderBar({
@@ -100,7 +105,7 @@ angular.module('ionic.ui.viewState', ['ionic.service.view', 'ionic.service.gestu
100105
$scope.backButtonEnabled = !!data;
101106
}),
102107
$scope.$parent.$on('viewState.titleUpdated', function(e, data) {
103-
$scope.titles[$scope.titles.length - 1] = data && data.title || '';
108+
$scope.title = data && data.title || '';
104109
})
105110
];
106111
$scope.$on('$destroy', function() {
@@ -109,19 +114,66 @@ angular.module('ionic.ui.viewState', ['ionic.service.view', 'ionic.service.gestu
109114
});
110115

111116
function updateHeaderData(data) {
112-
var newTitle = data && data.title || '';
113117

114-
$scope.isReverse = data.navDirection == 'back';
115-
116-
if (data.hideBackButton) {
117-
$scope.backButtonEnabled = false;
118+
if (angular.isDefined(data.hideBackButton)) {
119+
$scope.backButtonEnabled = !!data.hideBackButton;
118120
}
119-
121+
$scope.isReverse = data.navDirection == 'back';
120122
$scope.animateEnabled = !!(data.navDirection && data.animate !== false);
121-
$scope.titles.length = 0;
122-
$scope.titles.push(newTitle);
123+
123124
$scope.leftButtons = data.leftButtons;
124125
$scope.rightButtons = data.rightButtons;
126+
$scope.oldTitle = $scope.title;
127+
$scope.title = data && data.title || '';
128+
129+
//If no animation, we're done!
130+
if (!$scope.animateEnabled) {
131+
hb.align();
132+
return;
133+
} else {
134+
animateTitles();
135+
}
136+
}
137+
138+
function animateTitles() {
139+
var oldTitleEl, newTitleEl, currentTitles;
140+
141+
//If we have any title right now (or more than one, they could be transitioning on switch),
142+
//replace the first one with an oldTitle element
143+
currentTitles = $element[0].querySelectorAll('.title');
144+
if (currentTitles.length) {
145+
oldTitleEl = $compile('<h1 ng-bind-html="oldTitle" class="title"></h1>')($scope);
146+
angular.element(currentTitles[0]).replaceWith(oldTitleEl);
147+
}
148+
//Compile new title
149+
newTitleEl = $compile('<h1 class="title invisible" ng-bind-html="title"></h1>')($scope);
150+
151+
//Animate in one frame
152+
ionic.requestAnimationFrame(function() {
153+
154+
oldTitleEl && $animate.leave(angular.element(oldTitleEl));
155+
156+
var insert = oldTitleEl && angular.element(oldTitleEl) || null;
157+
$animate.enter(newTitleEl, $element, insert, function() {
158+
hb.align();
159+
});
160+
161+
//Cleanup any old titles leftover (besides the one we already did replaceWith on)
162+
angular.forEach(currentTitles, function(el) {
163+
if (el && el.parentNode) {
164+
//Use .remove() to cleanup things like .data()
165+
angular.element(el).remove();
166+
}
167+
});
168+
169+
//$apply so bindings fire
170+
$scope.$digest();
171+
172+
//Stop flicker of new title on ios7
173+
ionic.requestAnimationFrame(function() {
174+
newTitleEl[0].classList.remove('invisible');
175+
});
176+
});
125177
}
126178
};
127179
}

js/ext/angular/test/directive/ionicView.unit.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -51,23 +51,23 @@ describe('Ionic View', function() {
5151
function backButton() {
5252
return angular.element(element[0].querySelector('.back-button'));
5353
};
54-
expect(backButton().length).toEqual(1);
54+
expect(backButton().hasClass('hide')).toEqual(true);
5555

5656
scope.$broadcast('viewState.showBackButton', false);
5757
scope.$apply();
58-
expect(backButton().length).toEqual(0);
58+
expect(backButton().hasClass('hide')).toEqual(true);
5959

6060
scope.$broadcast('viewState.showBackButton', true);
6161
scope.$apply();
62-
expect(backButton().length).toEqual(1);
62+
expect(backButton().hasClass('hide')).toEqual(false);
6363

6464
scope.$broadcast('$viewHistory.historyChange', { showBack: false });
6565
scope.$apply();
66-
expect(backButton().length).toEqual(0);
66+
expect(backButton().hasClass('hide')).toEqual(true);
6767

6868
scope.$broadcast('$viewHistory.historyChange', { showBack: true });
6969
scope.$apply();
70-
expect(backButton().length).toEqual(1);
70+
expect(backButton().hasClass('hide')).toEqual(false);
7171
});
7272

7373
it('should show/hide navBar', function() {
@@ -113,7 +113,7 @@ describe('Ionic View', function() {
113113
scope.$digest();
114114
var navBar = element.find('header');
115115
var title = navBar.find('h1');
116-
expect(title.text().trim()).toEqual('Title');
116+
expect(element.find('header').find('h1').text().trim()).toEqual('Title');
117117

118118
scope.viewTitle = 'New Title';
119119
scope.$digest();

0 commit comments

Comments
 (0)