fix(navBar): animations, hide back button, no flicker

Closes #653
This commit is contained in:
Andy Joslin
2014-02-20 17:13:31 -05:00
parent 53aeacc08b
commit 465ea76969
2 changed files with 79 additions and 27 deletions

View File

@@ -23,7 +23,7 @@ angular.module('ionic.ui.viewState', ['ionic.service.view', 'ionic.service.gestu
* Our Nav Bar directive which updates as the controller state changes.
*/
.directive('ionNavBar', ['$ionicViewService', '$rootScope', '$animate', '$compile',
function( $ionicViewService, $rootScope, $animate, $compile) {
function( $ionicViewService, $rootScope, $animate, $compile) {
return {
restrict: 'E',
@@ -38,10 +38,10 @@ angular.module('ionic.ui.viewState', ['ionic.service.view', 'ionic.service.gestu
},
controller: function() {},
template:
'<header class="bar bar-header nav-bar {{type}} {{isReverse ? \'reverse\' : \'\'}} ' +
'{{isInvisible ? \'invisible\' : \'\'}} {{animateEnabled ? animation : \'\'}}">' +
'<ion-nav-back-button ng-if="backButtonEnabled && (backType || backLabel || backIcon)" ' +
'type="backType" label="backLabel" icon="backIcon" class="invisible" ion-async-visible>' +
'<header class="bar bar-header nav-bar{{navBarClass()}}">' +
'<ion-nav-back-button ng-if="(backType || backLabel || backIcon)" ' +
'type="backType" label="backLabel" icon="backIcon" class="hide" ' +
'ng-class="{hide: !backButtonEnabled}">' +
'</ion-nav-back-button>' +
'<div class="buttons left-buttons"> ' +
'<button ng-click="button.tap($event)" ng-repeat="button in leftButtons" ' +
@@ -49,25 +49,30 @@ angular.module('ionic.ui.viewState', ['ionic.service.view', 'ionic.service.gestu
'</button>' +
'</div>' +
//ng-repeat makes it easy to add new / remove old and have proper enter/leave anims
'<h1 ng-repeat="title in titles" ng-bind-html="title" class="title invisible" ion-async-visible ion-nav-bar-title></h1>' +
'<h1 ng-bind-html="title" class="title"></h1>' +
'<div class="buttons right-buttons" ng-if="rightButtons.length"> ' +
'<button ng-click="button.tap($event)" ng-repeat="button in rightButtons" '+
'class="button no-animation {{button.type}}" ng-bind-html="button.content">' +
'<div class="buttons right-buttons"> ' +
'<button ng-click="button.tap($event)" ng-repeat="button in rightButtons" '+
'class="button no-animation {{button.type}}" ng-bind-html="button.content">' +
'</button>' +
'</div>' +
'</header>',
compile: function(tElement, tAttrs) {
return function link($scope, $element, $attr) {
$scope.titles = [];
//defaults
$scope.backButtonEnabled = true;
$scope.backButtonEnabled = false;
$scope.animateEnabled = true;
$scope.isReverse = false;
$scope.isInvisible = true;
$scope.navBarClass = function() {
return ($scope.type ? ' ' + $scope.type : '') +
($scope.isReverse ? ' reverse' : '') +
($scope.isInvisible ? ' invisible' : '') +
(!$scope.animationDisabled && $scope.animation ? ' ' + $scope.animation : '');
};
// Initialize our header bar view which will handle
// resizing and aligning our title labels
var hb = new ionic.views.HeaderBar({
@@ -100,7 +105,7 @@ angular.module('ionic.ui.viewState', ['ionic.service.view', 'ionic.service.gestu
$scope.backButtonEnabled = !!data;
}),
$scope.$parent.$on('viewState.titleUpdated', function(e, data) {
$scope.titles[$scope.titles.length - 1] = data && data.title || '';
$scope.title = data && data.title || '';
})
];
$scope.$on('$destroy', function() {
@@ -109,19 +114,66 @@ angular.module('ionic.ui.viewState', ['ionic.service.view', 'ionic.service.gestu
});
function updateHeaderData(data) {
var newTitle = data && data.title || '';
$scope.isReverse = data.navDirection == 'back';
if (data.hideBackButton) {
$scope.backButtonEnabled = false;
if (angular.isDefined(data.hideBackButton)) {
$scope.backButtonEnabled = !!data.hideBackButton;
}
$scope.isReverse = data.navDirection == 'back';
$scope.animateEnabled = !!(data.navDirection && data.animate !== false);
$scope.titles.length = 0;
$scope.titles.push(newTitle);
$scope.leftButtons = data.leftButtons;
$scope.rightButtons = data.rightButtons;
$scope.oldTitle = $scope.title;
$scope.title = data && data.title || '';
//If no animation, we're done!
if (!$scope.animateEnabled) {
hb.align();
return;
} else {
animateTitles();
}
}
function animateTitles() {
var oldTitleEl, newTitleEl, currentTitles;
//If we have any title right now (or more than one, they could be transitioning on switch),
//replace the first one with an oldTitle element
currentTitles = $element[0].querySelectorAll('.title');
if (currentTitles.length) {
oldTitleEl = $compile('<h1 ng-bind-html="oldTitle" class="title"></h1>')($scope);
angular.element(currentTitles[0]).replaceWith(oldTitleEl);
}
//Compile new title
newTitleEl = $compile('<h1 class="title invisible" ng-bind-html="title"></h1>')($scope);
//Animate in one frame
ionic.requestAnimationFrame(function() {
oldTitleEl && $animate.leave(angular.element(oldTitleEl));
var insert = oldTitleEl && angular.element(oldTitleEl) || null;
$animate.enter(newTitleEl, $element, insert, function() {
hb.align();
});
//Cleanup any old titles leftover (besides the one we already did replaceWith on)
angular.forEach(currentTitles, function(el) {
if (el && el.parentNode) {
//Use .remove() to cleanup things like .data()
angular.element(el).remove();
}
});
//$apply so bindings fire
$scope.$digest();
//Stop flicker of new title on ios7
ionic.requestAnimationFrame(function() {
newTitleEl[0].classList.remove('invisible');
});
});
}
};
}

View File

@@ -51,23 +51,23 @@ describe('Ionic View', function() {
function backButton() {
return angular.element(element[0].querySelector('.back-button'));
};
expect(backButton().length).toEqual(1);
expect(backButton().hasClass('hide')).toEqual(true);
scope.$broadcast('viewState.showBackButton', false);
scope.$apply();
expect(backButton().length).toEqual(0);
expect(backButton().hasClass('hide')).toEqual(true);
scope.$broadcast('viewState.showBackButton', true);
scope.$apply();
expect(backButton().length).toEqual(1);
expect(backButton().hasClass('hide')).toEqual(false);
scope.$broadcast('$viewHistory.historyChange', { showBack: false });
scope.$apply();
expect(backButton().length).toEqual(0);
expect(backButton().hasClass('hide')).toEqual(true);
scope.$broadcast('$viewHistory.historyChange', { showBack: true });
scope.$apply();
expect(backButton().length).toEqual(1);
expect(backButton().hasClass('hide')).toEqual(false);
});
it('should show/hide navBar', function() {
@@ -113,7 +113,7 @@ describe('Ionic View', function() {
scope.$digest();
var navBar = element.find('header');
var title = navBar.find('h1');
expect(title.text().trim()).toEqual('Title');
expect(element.find('header').find('h1').text().trim()).toEqual('Title');
scope.viewTitle = 'New Title';
scope.$digest();