From 05fb7a09ea9d27d181899e7cc4757909c493efcb Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Wed, 26 Nov 2014 12:46:14 -0600 Subject: [PATCH] refactor(backButton): separate show/hide logic --- js/angular/controller/headerBarController.js | 35 ++++++-- js/angular/controller/navBarController.js | 42 ++++++---- js/angular/controller/navViewController.js | 16 +++- js/angular/directive/navBackButton.js | 2 +- scss/_bar.scss | 1 - .../controller/navBarController.unit.js | 84 ++++++++++++++++++- .../angular/directive/navBackButton.unit.js | 2 +- 7 files changed, 151 insertions(+), 31 deletions(-) diff --git a/js/angular/controller/headerBarController.js b/js/angular/controller/headerBarController.js index 5943543334..4dc6e42c25 100644 --- a/js/angular/controller/headerBarController.js +++ b/js/angular/controller/headerBarController.js @@ -23,6 +23,8 @@ function($scope, $element, $attrs, $q, $ionicConfig, $ionicHistory) { var titleCss = ''; var isBackEnabled = false; var isBackShown = true; + var isNavBackShown = true; + var isBackElementShown = false; var titleTextWidth = 0; @@ -41,30 +43,45 @@ function($scope, $element, $attrs, $q, $ionicConfig, $ionicHistory) { }; - self.enableBack = function(shouldEnable) { + self.enableBack = function(shouldEnable, disableReset) { // whether or not the back button show be visible, according // to the navigation and history - if (arguments.length && shouldEnable !== isBackEnabled) { - var backBtnEle = getEle(BACK_BUTTON); - backBtnEle && backBtnEle.classList[ shouldEnable ? 'remove' : 'add' ]('back-disabled'); + if (arguments.length) { isBackEnabled = shouldEnable; + if (!disableReset) self.updateBackButton(); } return isBackEnabled; }; - self.showBack = function(shouldShow) { + self.showBack = function(shouldShow, disableReset) { // different from enableBack() because this will always have the back // visually hidden if false, even if the history says it should show - if (arguments.length && shouldShow !== isBackShown) { - var backBtnEle = getEle(BACK_BUTTON); - backBtnEle && backBtnEle.classList[ shouldShow ? 'remove' : 'add' ](HIDE); + if (arguments.length) { isBackShown = shouldShow; + if (!disableReset) self.updateBackButton(); } return isBackShown; }; + self.showNavBack = function(shouldShow) { + // different from showBack() because this is for the entire nav bar's + // setting for all of it's child headers. For internal use. + isNavBackShown = shouldShow; + self.updateBackButton(); + }; + + + self.updateBackButton = function() { + if ( (isBackShown && isNavBackShown && isBackEnabled) !== isBackElementShown) { + isBackElementShown = isBackShown && isNavBackShown && isBackEnabled; + var backBtnEle = getEle(BACK_BUTTON); + backBtnEle && backBtnEle.classList[ isBackElementShown ? 'remove' : 'add' ](HIDE); + } + }; + + self.titleTextWidth = function() { if (!titleTextWidth) { var bounds = ionic.DomUtil.getTextBounds(getEle(TITLE)); @@ -320,7 +337,7 @@ function($scope, $element, $attrs, $q, $ionicConfig, $ionicHistory) { var eleCache = {}; function getEle(className) { - if (!eleCache[className]) { + if (!isDefined(eleCache[className])) { eleCache[className] = $element[0].querySelector('.' + className); } return eleCache[className]; diff --git a/js/angular/controller/navBarController.js b/js/angular/controller/navBarController.js index d14365d8ac..7ef548066a 100644 --- a/js/angular/controller/navBarController.js +++ b/js/angular/controller/navBarController.js @@ -82,12 +82,6 @@ function($scope, $element, $attrs, $compile, $timeout, $ionicNavBarDelegate, $io var headerBarInstance = { isActive: isActive, - enableBack: function(shouldEnable) { - headerBarCtrl.enableBack(shouldEnable); - }, - showBack: function(shouldShow) { - headerBarCtrl.showBack(shouldShow); - }, title: function(newTitleText) { headerBarCtrl.title(newTitleText); }, @@ -215,10 +209,12 @@ function($scope, $element, $attrs, $compile, $timeout, $ionicNavBarDelegate, $io self.enable(showNavBar); var enteringHeaderBar = self.isInitialized ? getOffScreenHeaderBar() : getOnScreenHeaderBar(); var leavingHeaderBar = self.isInitialized ? getOnScreenHeaderBar() : null; + var enteringHeaderCtrl = enteringHeaderBar.controller(); // update if the entering header should show the back button or not - self.enableBackButton(viewData.enableBack, enteringHeaderBar); - self.showBackButton(viewData.showBack, enteringHeaderBar); + enteringHeaderCtrl.enableBack(viewData.enableBack, true); + enteringHeaderCtrl.showBack(viewData.showBack, true); + enteringHeaderCtrl.updateBackButton(); // update the entering header bar's title self.title(viewData.title, enteringHeaderBar); @@ -335,17 +331,31 @@ function($scope, $element, $attrs, $compile, $timeout, $ionicNavBarDelegate, $io }; - self.enableBackButton = function(shouldEnable, headerBar) { - headerBar = headerBar || getOnScreenHeaderBar(); - headerBar && headerBar.enableBack(shouldEnable); + /** + * @ngdoc method + * @name $ionicNavBar#showBackButton + * @description Show/hide the nav bar back button when there is a + * back view. If the back button is not possible, for example, the + * first view in the stack, then this will not force the back button + * to show. + */ + self.showBackButton = function(shouldShow) { + for (var x = 0; x < headerBars.length; x++) { + headerBars[x].controller().showNavBack(!!shouldShow); + } + $scope.$isBackButtonShown = !!shouldShow; + return $scope.$isBackButtonShown; }; - self.showBackButton = function(shouldShow, headerBar) { - headerBar = headerBar || getOnScreenHeaderBar(); - headerBar && headerBar.showBack(shouldShow); - $scope.$isBackButtonShown = !!shouldShow; - return !!shouldShow; + /** + * @ngdoc method + * @name $ionicNavBar#showActiveBackButton + * @description Show/hide only the active header bar's back button. + */ + self.showActiveBackButton = function(shouldShow) { + var headerBar = getOnScreenHeaderBar(); + headerBar && headerBar.controller().showBack(shouldShow); }; diff --git a/js/angular/controller/navViewController.js b/js/angular/controller/navViewController.js index 0bf842ca58..6c9b350b2d 100644 --- a/js/angular/controller/navViewController.js +++ b/js/angular/controller/navViewController.js @@ -202,15 +202,29 @@ function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate, }; + /** + * @ngdoc method + * @name $ionicNavView#enableBackButton + * @description Enable/disable if the back button can be shown or not. For + * example, the very first view in the navigation stack would not have a + * back view, so the back button would be disabled. + */ self.enableBackButton = function(shouldEnable) { var associatedNavBarCtrl = getAssociatedNavBarCtrl(); associatedNavBarCtrl && associatedNavBarCtrl.enableBackButton(shouldEnable); }; + /** + * @ngdoc method + * @name $ionicNavView#showBackButton + * @description Show/hide the nav bar active back button. If the back button + * is not possible this will not force the back button to show. The + * `enableBackButton()` method handles if a back button is even possible or not. + */ self.showBackButton = function(shouldShow) { var associatedNavBarCtrl = getAssociatedNavBarCtrl(); - associatedNavBarCtrl && associatedNavBarCtrl.showBackButton(shouldShow); + associatedNavBarCtrl && associatedNavBarCtrl.showActiveBackButton(shouldShow); }; diff --git a/js/angular/directive/navBackButton.js b/js/angular/directive/navBackButton.js index 328318fb81..2edb95245f 100644 --- a/js/angular/directive/navBackButton.js +++ b/js/angular/directive/navBackButton.js @@ -71,7 +71,7 @@ IonicModule buttonEle.setAttribute('ng-click', '$ionicGoBack($event)'); } - buttonEle.className = 'button back-button back-disabled buttons ' + (tElement.attr('class') || ''); + buttonEle.className = 'button back-button hide buttons ' + (tElement.attr('class') || ''); buttonEle.innerHTML = tElement.html() || ''; var childNode; diff --git a/scss/_bar.scss b/scss/_bar.scss index 3320b3a190..f147b26e9e 100644 --- a/scss/_bar.scss +++ b/scss/_bar.scss @@ -361,7 +361,6 @@ z-index: $z-index-bar; } -.bar .back-button.back-disabled, .bar .back-button.hide, .bar .buttons .hide { display: none; diff --git a/test/unit/angular/controller/navBarController.unit.js b/test/unit/angular/controller/navBarController.unit.js index 3cc1556b8d..114de02208 100644 --- a/test/unit/angular/controller/navBarController.unit.js +++ b/test/unit/angular/controller/navBarController.unit.js @@ -1,14 +1,15 @@ describe('$ionicNavBar controller', function() { beforeEach(module('ionic')); - var scope; + var scope, ele; function makeNavBarCtrl(css) { var ctrl; + ele = angular.element('
'); inject(function($rootScope, $controller, $ionicHistory, $ionicViewSwitcher) { scope = $rootScope.$new(); ctrl = $controller('$ionicNavBar', { $scope: scope, - $element: angular.element('
'), + $element: ele, $attrs: { class: css }, $ionicHistory: $ionicHistory, $ionicViewSwitcher: $ionicViewSwitcher @@ -253,4 +254,83 @@ describe('$ionicNavBar controller', function() { expect(backButtonEle.text()).toBe(scope.buttonText); }); + it('should show the active back button on update() w/ showBack false', function() { + var ctrl = makeNavBarCtrl(); + ctrl.navElement('backButton', ''); + ctrl.init(); + + ctrl.update({ + enableBack: true, + showBack: false + }); + + var backButtonEle1 = ele[0].querySelector('[nav-bar]:first-child .back-button'); + var backButtonEle2 = ele[0].querySelector('[nav-bar]:last-child .back-button'); + expect(backButtonEle1.classList.contains('hide')).toBe(true); + expect(backButtonEle2.classList.contains('hide')).toBe(true); + + ctrl.update({ + enableBack: true, + showBack: true + }); + + expect(backButtonEle1.classList.contains('hide')).toBe(false); + expect(backButtonEle2.classList.contains('hide')).toBe(true); + }); + + it('should not show the back button on update() w/ enableBack false', function() { + var ctrl = makeNavBarCtrl(); + ctrl.navElement('backButton', ''); + ctrl.init(); + + ctrl.update({ + enableBack: false, + showBack: true + }); + + var backButtonEle1 = ele[0].querySelector('[nav-bar]:first-child .back-button'); + var backButtonEle2 = ele[0].querySelector('[nav-bar]:last-child .back-button'); + expect(backButtonEle1.classList.contains('hide')).toBe(true); + expect(backButtonEle2.classList.contains('hide')).toBe(true); + + ctrl.update({ + enableBack: true, + showBack: true + }); + expect(backButtonEle1.classList.contains('hide')).toBe(false); + expect(backButtonEle2.classList.contains('hide')).toBe(true); + }); + + it('should always hide the back buttons on showBackButton(false)', function() { + var ctrl = makeNavBarCtrl(); + ctrl.navElement('backButton', ''); + ctrl.init(); + + ctrl.showBackButton(false); + ctrl.update({ + enableBack: true, + showBack: true + }); + + var backButtonEle1 = ele[0].querySelector('[nav-bar]:first-child .back-button'); + var backButtonEle2 = ele[0].querySelector('[nav-bar]:last-child .back-button'); + expect(backButtonEle1.classList.contains('hide')).toBe(true); + expect(backButtonEle2.classList.contains('hide')).toBe(true); + + ctrl.showBackButton(true); + ctrl.update({ + enableBack: true, + showBack: true + }); + expect(backButtonEle1.classList.contains('hide')).toBe(false); + expect(backButtonEle2.classList.contains('hide')).toBe(false); + + ctrl.update({ + enableBack: false, + showBack: true + }); + expect(backButtonEle1.classList.contains('hide')).toBe(true); + expect(backButtonEle2.classList.contains('hide')).toBe(false); + }); + }); diff --git a/test/unit/angular/directive/navBackButton.unit.js b/test/unit/angular/directive/navBackButton.unit.js index 2c73686753..5a8e9804b5 100644 --- a/test/unit/angular/directive/navBackButton.unit.js +++ b/test/unit/angular/directive/navBackButton.unit.js @@ -34,7 +34,7 @@ describe('ionNavBackButton directive', function() { expect( outputEle[0].tagName ).toBe('BUTTON'); expect( outputEle.hasClass('button') ).toBe(true); expect( outputEle.hasClass('back-button') ).toBe(true); - expect( outputEle.hasClass('back-disabled') ).toBe(true); + expect( outputEle.hasClass('hide') ).toBe(true); expect( outputEle.hasClass('buttons') ).toBe(true); }));