diff --git a/dist/css/ionic.css b/dist/css/ionic.css index 1d4fc8c3f8..38646fb22c 100644 --- a/dist/css/ionic.css +++ b/dist/css/ionic.css @@ -2949,6 +2949,79 @@ a.button { * and pretty easy on performance. They can be overidden * and enhanced easily. */ +.noop-animation > .ng-enter, .noop-animation.ng-enter, .noop-animation > .ng-leave, .noop-animation.ng-leave { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + -webkit-transition: all cubic-bezier(0.25, 0.46, 0.45, 0.94) 250ms; + transition: all cubic-bezier(0.25, 0.46, 0.45, 0.94) 250ms; } + +.slide-left-right > .ng-enter, .slide-left-right.ng-enter, .slide-left-right > .ng-leave, .slide-left-right.ng-leave { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + -webkit-transition: all cubic-bezier(0.25, 0.46, 0.45, 0.94) 250ms; + transition: all cubic-bezier(0.25, 0.46, 0.45, 0.94) 250ms; } +.slide-left-right > .ng-enter, .slide-left-right.ng-enter { + -webkit-transform: translate3d(100%, 0, 0); } +.slide-left-right > .ng-enter.ng-enter-active, .slide-left-right.ng-enter.ng-enter-active { + -webkit-transform: translate3d(0, 0, 0); } +.slide-left-right > .ng-leave.ng-leave-active, .slide-left-right.ng-leave.ng-leave-active { + -webkit-transform: translate3d(-100%, 0, 0); } +.slide-left-right.reverse > .ng-enter, .slide-left-right.reverse.ng-enter, .slide-left-right.reverse > .ng-leave, .slide-left-right.reverse.ng-leave { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + -webkit-transition: all cubic-bezier(0.25, 0.46, 0.45, 0.94) 250ms; + transition: all cubic-bezier(0.25, 0.46, 0.45, 0.94) 250ms; } +.slide-left-right.reverse > .ng-enter, .slide-left-right.reverse.ng-enter { + -webkit-transform: translate3d(-100%, 0, 0); } +.slide-left-right.reverse > .ng-enter.ng-enter-active, .slide-left-right.reverse.ng-enter.ng-enter-active { + -webkit-transform: translate3d(0, 0, 0); } +.slide-left-right.reverse > .ng-leave.ng-leave-active, .slide-left-right.reverse.ng-leave.ng-leave-active { + -webkit-transform: translate3d(100%, 0, 0); } + +.slide-left-right-ios7 > .ng-enter, .slide-left-right-ios7.ng-enter, .slide-left-right-ios7 > .ng-leave, .slide-left-right-ios7.ng-leave { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + -webkit-transition: all 250ms; + -webkit-transition-timing-function: cubic-bezier(0.1, 0.7, 0.1, 1); + transition: all cubic-bezier(0.25, 0.46, 0.45, 0.94) 250ms; + transition: all 250ms; + transition-timing-function: cubic-bezier(0.1, 0.7, 0.1, 1); + box-shadow: -1px 0px 3px rgba(0, 0, 0, 0.2), 1px 0px 3px rgba(0, 0, 0, 0.2); } +.slide-left-right-ios7 > .ng-enter, .slide-left-right-ios7.ng-enter { + -webkit-transform: translate3d(100%, 0, 0); } +.slide-left-right-ios7 > .ng-enter.ng-enter-active, .slide-left-right-ios7.ng-enter.ng-enter-active { + -webkit-transform: translate3d(0, 0, 0); } +.slide-left-right-ios7 > .ng-leave.ng-leave-active, .slide-left-right-ios7.ng-leave.ng-leave-active { + -webkit-transform: translate3d(-20%, 0, 0); } +.slide-left-right-ios7.reverse > .ng-enter, .slide-left-right-ios7.reverse.ng-enter, .slide-left-right-ios7.reverse > .ng-leave, .slide-left-right-ios7.reverse.ng-leave { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + -webkit-transition: all 250ms; + -webkit-transition-timing-function: cubic-bezier(0.1, 0.7, 0.1, 1); + transition: all cubic-bezier(0.25, 0.46, 0.45, 0.94) 250ms; + transition-timing-function: cubic-bezier(0.1, 0.7, 0.1, 1); } +.slide-left-right-ios7.reverse > .ng-enter, .slide-left-right-ios7.reverse.ng-enter { + -webkit-transform: translate3d(-20%, 0, 0); } +.slide-left-right-ios7.reverse > .ng-enter.ng-enter-active, .slide-left-right-ios7.reverse.ng-enter.ng-enter-active { + -webkit-transform: translate3d(0, 0, 0); } +.slide-left-right-ios7.reverse > .ng-leave.ng-leave-active, .slide-left-right-ios7.reverse.ng-leave.ng-leave-active { + -webkit-transform: translate3d(100%, 0, 0); } + @-webkit-keyframes slideInLeft { 0% { -webkit-transform: translate3d(100%, 0, 0); } @@ -3172,6 +3245,41 @@ a.button { -webkit-transform: rotate(360deg); transform: rotate(360deg); } } +/** + * Some component specific animations + */ +.nav-title-slide-ios7 > .ng-enter, .nav-title-slide-ios7.ng-enter, .nav-title-slide-ios7 > .ng-leave, .nav-title-slide-ios7.ng-leave { + -webkit-transition: all 250ms; + -webkit-transition-timing-function: cubic-bezier(0.1, 0.7, 0.1, 1); + transition: all 250ms; + transition-timing-function: cubic-bezier(0.1, 0.7, 0.1, 1); + opacity: 1; } +.nav-title-slide-ios7 > .ng-enter, .nav-title-slide-ios7.ng-enter { + opacity: 0; + -webkit-transform: translate3d(30%, 0, 0); } +.nav-title-slide-ios7 > .ng-enter.ng-enter-active, .nav-title-slide-ios7.ng-enter.ng-enter-active { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); } +.nav-title-slide-ios7 > .ng-leave.ng-leave-active, .nav-title-slide-ios7.ng-leave.ng-leave-active { + opacity: 0; + -webkit-transform: translate3d(-30%, 0, 0); } + +.reverse .nav-title-slide-ios7 > .ng-enter, .reverse .nav-title-slide-ios7.ng-enter, .reverse .nav-title-slide-ios7 > .ng-leave, .reverse .nav-title-slide-ios7.ng-leave { + -webkit-transition: all 250ms; + -webkit-transition-timing-function: cubic-bezier(0.1, 0.7, 0.1, 1); + transition: all 250ms; + transition-timing-function: cubic-bezier(0.1, 0.7, 0.1, 1); + opacity: 1; } +.reverse .nav-title-slide-ios7 > .ng-enter, .reverse .nav-title-slide-ios7.ng-enter { + opacity: 0; + -webkit-transform: translate3d(-30%, 0, 0); } +.reverse .nav-title-slide-ios7 > .ng-enter.ng-enter-active, .reverse .nav-title-slide-ios7.ng-enter.ng-enter-active { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); } +.reverse .nav-title-slide-ios7 > .ng-leave.ng-leave-active, .reverse .nav-title-slide-ios7.ng-leave.ng-leave-active { + opacity: 0; + -webkit-transform: translate3d(30%, 0, 0); } + /** * Grid * -------------------------------------------------- diff --git a/dist/js/ionic-angular.js b/dist/js/ionic-angular.js index 6805a8a0e9..2f07db83d5 100644 --- a/dist/js/ionic-angular.js +++ b/dist/js/ionic-angular.js @@ -23941,7 +23941,7 @@ angular.module('ionic.service', [ angular.module('ionic.ui', [ 'ionic.ui.content', 'ionic.ui.tabs', - 'ionic.ui.nav', + 'ionic.ui.navRouter', 'ionic.ui.header', 'ionic.ui.sideMenu', 'ionic.ui.slideBox', @@ -23957,6 +23957,7 @@ angular.module('ionic', [ // Angular deps 'ngAnimate', + 'ngRoute', 'ngTouch', 'ngSanitize' ]); @@ -24909,6 +24910,10 @@ angular.module('ionic.ui.loading', []) (function() { 'use strict'; +/** + * Note: currently unused + */ + /** * @description * The NavController is a navigation stack View Controller modelled off of @@ -25254,6 +25259,350 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges } }) +})(); +; +(function() { +'use strict'; + +/** + * @description + * The NavController is a navigation stack View Controller modelled off of + * UINavigationController from Cocoa Touch. With the Nav Controller, you can + * "push" new "pages" on to the navigation stack, and then pop them off to go + * back. The NavController controls a navigation bar with a back button and title + * which updates as the pages switch. + * + * The NavController makes sure to not recycle scopes of old pages + * so that a pop will still show the same state that the user left. + * + * However, once a page is popped, its scope is destroyed and will have to be + * recreated then next time it is pushed. + * + */ + +var actualLocation = null; + +angular.module('ionic.ui.navRouter', []) + +.run(['$rootScope', function($rootScope) { + $rootScope.stackCursorPosition = 0; +}]) + +.directive('navRouter', ['$rootScope', '$timeout', '$location', '$window', '$route', function($rootScope, $timeout, $location, $window, $route) { + return { + restrict: 'AC', + // So you can require being under this + controller: ['$scope', '$element', function($scope, $element) { + this.navBar = { + isVisible: true + }; + $scope.navController = this; + }], + + link: function($scope, $element, $attr) { + $scope.animation = $attr.animation; + + $element.addClass('noop-animation'); + + var isFirst = true; + + var initTransition = function() { + //$element.addClass($scope.animation); + }; + + var reverseTransition = function() { + $element.removeClass('noop-animation'); + $element.addClass($scope.animation); + $element.addClass('reverse'); + }; + + var forwardTransition = function() { + $element.removeClass('noop-animation'); + $element.removeClass('reverse'); + $element.addClass($scope.animation); + }; + + $scope.$on('$routeChangeSuccess', function(e, a) { + }); + $scope.$on('$routeChangeStart', function(e, next, current) { + var back, historyState = $window.history.state; + + back = !!(historyState && historyState.position <= $rootScope.stackCursorPosition); + + if(isFirst || (next && next.$$route.originalPath === "")) { + // Don't animate + return; + } + + if(back) { + reverseTransition(); + } else { + forwardTransition(); + } + }); + + $scope.$on('$locationChangeSuccess', function(a, b, c) { + // Store the new location + $rootScope.actualLocation = $location.path(); + if(isFirst) { + isFirst = false; + initTransition(); + } + }); + + + // Keep track of location changes and update a stack pointer that tracks whether we are + // going forwards or back + $scope.$watch(function () { return $location.path() }, function (newLocation, oldLocation) { + if($rootScope.actualLocation === newLocation) { + + var back, historyState = $window.history.state; + + back = !!(historyState && historyState.position <= $rootScope.stackCursorPosition); + + if (back) { + //back button + $rootScope.stackCursorPosition--; + } else { + //forward button + $rootScope.stackCursorPosition++; + } + + } else { + var currentRouteBeforeChange = $route.current; + + if (currentRouteBeforeChange) { + + $window.history.replaceState({ + position: $rootScope.stackCursorPosition + }); + + $rootScope.stackCursorPosition++; + } + } + }); + } + } +}]) + +/** + * Our Nav Bar directive which updates as the controller state changes. + */ +.directive('navBar', ['$rootScope', '$animate', '$compile', function($rootScope, $animate, $compile) { + var animate = function($scope, $element, oldTitle, data, cb) { + var title, nTitle, oTitle, titles = $element[0].querySelectorAll('.title'); + + var newTitle = data.title; + if(!oldTitle || oldTitle === newTitle) { + cb(); + return; + } + + title = angular.element(titles[0]); + oTitle = $compile('

')($scope); + title.replaceWith(oTitle); + nTitle = $compile('

')($scope); + + var insert = $element[0].firstElementChild || null; + + $animate.enter(nTitle, $element, insert && angular.element(insert), function() { + cb(); + }); + $animate.leave(angular.element(oTitle), function() { + }); + + $scope.$on('navRouter.rightButtonsChanged', function(e, buttons) { + console.log('Buttons changing for nav bar', buttons); + }); + }; + + return { + restrict: 'E', + require: '^navRouter', + replace: true, + scope: { + type: '@', + backButtonType: '@', + backButtonLabel: '@', + backButtonIcon: '@', + alignTitle: '@', + }, + template: '', + link: function($scope, $element, $attr, navCtrl) { + var backButton; + + // Create the back button content and show/hide it based on scope settings + $scope.enableBackButton = true; + $scope.backButtonContent = ''; + if($scope.backButtonIcon) { + $scope.backButtonContent += ''; + } + if($scope.backButtonLabel) { + $scope.backButtonContent += ' ' + $scope.backButtonLabel + } + + // Listen for changes in the stack cursor position to indicate whether a back + // button should be shown (this can still be disabled by the $scope.enableBackButton + $rootScope.$watch('stackCursorPosition', function(value) { + if(value > 0) { + $scope.showBackButton = true; + } else { + $scope.showBackButton = false; + } + }); + + // Store a reference to our nav controller + $scope.navController = navCtrl; + + // Initialize our header bar view which will handle resizing and aligning our title labels + var hb = new ionic.views.HeaderBar({ + el: $element[0], + alignTitle: $scope.alignTitle || 'center' + }); + $scope.headerBarView = hb; + + // Add the type of header bar class to this element + $element.addClass($scope.type); + + var updateHeaderData = function(data) { + console.log('Header data changed', data); + + var oldTitle = $scope.currentTitle; + $scope.oldTitle = oldTitle; + + if(typeof data.title !== 'undefined') { + $scope.currentTitle = data.title; + } + + if(typeof data.leftButtons !== 'undefined') { + $scope.leftButtons = data.leftButtons; + } + if(typeof data.rightButtons !== 'undefined') { + $scope.rightButtons = data.rightButtons; + } + if(typeof data.hideBackButton !== 'undefined') { + $scope.enableBackButton = data.hideBackButton !== true; + } + + if(data.animate !== false && typeof data.title !== 'undefined') { + animate($scope, $element, oldTitle, data, function() { + hb.align(); + }); + } else { + hb.align(); + } + }; + + // Listen for changes on title change, and update the title + $scope.$parent.$on('navRouter.pageChanged', function(e, data) { + updateHeaderData(data); + }); + + $scope.$parent.$on('navRouter.pageShown', function(e, data) { + updateHeaderData(data); + }); + + + /* + $scope.$parent.$on('navigation.push', function() { + backButton = angular.element($element[0].querySelector('.button')); + backButton.addClass($scope.backButtonType); + hb.align(); + }); + $scope.$parent.$on('navigation.pop', function() { + hb.align(); + }); + */ + + $scope.$on('$destroy', function() { + // + }); + } + }; +}]) + +.directive('navPage', ['$parse', function($parse) { + return { + restrict: 'E', + scope: true, + require: '^navRouter', + link: function($scope, $element, $attr, navCtrl) { + $element.addClass('pane'); + + $scope.icon = $attr.icon; + $scope.iconOn = $attr.iconOn; + $scope.iconOff = $attr.iconOff; + + // Should we hide a back button when this tab is shown + $scope.hideBackButton = $scope.$eval($attr.hideBackButton); + + // Whether we should animate on tab change, also impacts whether we + // tell any parent nav controller to animate + $scope.animate = $scope.$eval($attr.animate); + + // Grab whether we should update any parent nav router on tab changes + $scope.doesUpdateNavRouter = $scope.$eval($attr.doesUpdateNavRouter) || true; + + // watch for changes in the left buttons + var leftButtonsGet = $parse($attr.leftButtons); + $scope.$watch(leftButtonsGet, function(value) { + $scope.leftButtons = value; + if($scope.doesUpdateNavRouter) { + $scope.$emit('navRouter.leftButtonsChanged', $scope.rightButtons); + } + }); + + // watch for changes in the right buttons + var rightButtonsGet = $parse($attr.rightButtons); + $scope.$watch(rightButtonsGet, function(value) { + $scope.rightButtons = value; + }); + + // watch for changes in the title + var titleGet = $parse($attr.title); + $scope.$watch(titleGet, function(value) { + $scope.title = value; + $scope.$emit('navRouter.pageChanged', { + title: value, + animate: $scope.animate + }); + }); + + } + } +}]) + +.directive('navBack', ['$window', '$rootScope', function($window, $rootScope) { + return { + restrict: 'AC', + require: '^?navRouter', + link: function($scope, $element, $attr, navCtrl) { + var goBack = function() { + // Only trigger back if the stack is greater than zero + if($rootScope.stackCursorPosition > 0) { + $window.history.back(); + } + }; + $element.bind('tap', goBack); + $element.bind('click', goBack); + + $scope.$on('$destroy', function() { + $element.unbind('tap', goBack); + $element.unbind('click', goBack); + }); + } + } +}]); + })(); ; (function(ionic) { @@ -25642,7 +25991,7 @@ angular.module('ionic.ui.tabs', ['ngAnimate']) }) // Generic controller directive -.directive('tab', ['$animate', function($animate) { +.directive('tab', ['$animate', '$parse', function($animate, $parse) { return { restrict: 'E', replace: true, @@ -25657,6 +26006,30 @@ angular.module('ionic.ui.tabs', ['ngAnimate']) $scope.icon = $attr.icon; $scope.iconOn = $attr.iconOn; $scope.iconOff = $attr.iconOff; + + // Should we hide a back button when this tab is shown + $scope.hideBackButton = $scope.$eval($attr.hideBackButton); + + // Whether we should animate on tab change, also impacts whether we + // tell any parent nav controller to animate + $scope.animate = $scope.$eval($attr.animate); + + // Grab whether we should update any parent nav router on tab changes + $scope.doesUpdateNavRouter = $scope.$eval($attr.doesUpdateNavRouter) || true; + + var leftButtonsGet = $parse($attr.leftButtons); + $scope.$watch(leftButtonsGet, function(value) { + $scope.leftButtons = value; + if($scope.doesUpdateNavRouter) { + $scope.$emit('navRouter.leftButtonsChanged', $scope.rightButtons); + } + }); + + var rightButtonsGet = $parse($attr.rightButtons); + $scope.$watch(rightButtonsGet, function(value) { + $scope.rightButtons = value; + }); + tabsCtrl.add($scope); $scope.$watch('isVisible', function(value) { @@ -25676,6 +26049,19 @@ angular.module('ionic.ui.tabs', ['ngAnimate']) childElement.addClass('view-full'); $animate.enter(clone, $element.parent(), $element); + if($scope.title) { + // Send the title up in case we are inside of a nav controller + if($scope.doesUpdateNavRouter) { + $scope.$emit('navRouter.pageShown', { + title: $scope.title, + rightButtons: $scope.rightButtons, + leftButtons: $scope.leftButtons, + hideBackButton: $scope.hideBackButton || false, + animate: $scope.animate || true + }); + } + //$scope.$emit('navRouter.titleChanged', $scope.title); + } $scope.$broadcast('tab.shown'); }); } diff --git a/ionic.conf.js b/ionic.conf.js index 5c6911ab3b..8e40a2fb25 100644 --- a/ionic.conf.js +++ b/ionic.conf.js @@ -17,11 +17,6 @@ module.exports = function(config) { // Include jQuery only for testing convience (lots of DOM checking for unit tests on directives) 'https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js', - 'https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.2/angular.min.js', - 'https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.2/angular-mocks.js', - 'https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.2/angular-touch.js', - 'https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.2/angular-animate.js', - 'dist/js/ionic.js', 'dist/js/ionic-angular.js', diff --git a/js/ext/angular/src/directive/ionicNav.js b/js/ext/angular/src/directive/ionicNav.js index 4e65b828c9..4b2166aa82 100644 --- a/js/ext/angular/src/directive/ionicNav.js +++ b/js/ext/angular/src/directive/ionicNav.js @@ -1,6 +1,10 @@ (function() { 'use strict'; +/** + * Note: currently unused + */ + /** * @description * The NavController is a navigation stack View Controller modelled off of diff --git a/js/ext/angular/src/directive/ionicNavRouter.js b/js/ext/angular/src/directive/ionicNavRouter.js new file mode 100644 index 0000000000..fdc0fd9b20 --- /dev/null +++ b/js/ext/angular/src/directive/ionicNavRouter.js @@ -0,0 +1,343 @@ +(function() { +'use strict'; + +/** + * @description + * The NavController is a navigation stack View Controller modelled off of + * UINavigationController from Cocoa Touch. With the Nav Controller, you can + * "push" new "pages" on to the navigation stack, and then pop them off to go + * back. The NavController controls a navigation bar with a back button and title + * which updates as the pages switch. + * + * The NavController makes sure to not recycle scopes of old pages + * so that a pop will still show the same state that the user left. + * + * However, once a page is popped, its scope is destroyed and will have to be + * recreated then next time it is pushed. + * + */ + +var actualLocation = null; + +angular.module('ionic.ui.navRouter', []) + +.run(['$rootScope', function($rootScope) { + $rootScope.stackCursorPosition = 0; +}]) + +.directive('navRouter', ['$rootScope', '$timeout', '$location', '$window', '$route', function($rootScope, $timeout, $location, $window, $route) { + return { + restrict: 'AC', + // So you can require being under this + controller: ['$scope', '$element', function($scope, $element) { + this.navBar = { + isVisible: true + }; + $scope.navController = this; + }], + + link: function($scope, $element, $attr) { + $scope.animation = $attr.animation; + + $element.addClass('noop-animation'); + + var isFirst = true; + + var initTransition = function() { + //$element.addClass($scope.animation); + }; + + var reverseTransition = function() { + $element.removeClass('noop-animation'); + $element.addClass($scope.animation); + $element.addClass('reverse'); + }; + + var forwardTransition = function() { + $element.removeClass('noop-animation'); + $element.removeClass('reverse'); + $element.addClass($scope.animation); + }; + + $scope.$on('$routeChangeSuccess', function(e, a) { + }); + $scope.$on('$routeChangeStart', function(e, next, current) { + var back, historyState = $window.history.state; + + back = !!(historyState && historyState.position <= $rootScope.stackCursorPosition); + + if(isFirst || (next && next.$$route.originalPath === "")) { + // Don't animate + return; + } + + if(back) { + reverseTransition(); + } else { + forwardTransition(); + } + }); + + $scope.$on('$locationChangeSuccess', function(a, b, c) { + // Store the new location + $rootScope.actualLocation = $location.path(); + if(isFirst) { + isFirst = false; + initTransition(); + } + }); + + + // Keep track of location changes and update a stack pointer that tracks whether we are + // going forwards or back + $scope.$watch(function () { return $location.path() }, function (newLocation, oldLocation) { + if($rootScope.actualLocation === newLocation) { + + var back, historyState = $window.history.state; + + back = !!(historyState && historyState.position <= $rootScope.stackCursorPosition); + + if (back) { + //back button + $rootScope.stackCursorPosition--; + } else { + //forward button + $rootScope.stackCursorPosition++; + } + + } else { + var currentRouteBeforeChange = $route.current; + + if (currentRouteBeforeChange) { + + $window.history.replaceState({ + position: $rootScope.stackCursorPosition + }); + + $rootScope.stackCursorPosition++; + } + } + }); + } + } +}]) + +/** + * Our Nav Bar directive which updates as the controller state changes. + */ +.directive('navBar', ['$rootScope', '$animate', '$compile', function($rootScope, $animate, $compile) { + var animate = function($scope, $element, oldTitle, data, cb) { + var title, nTitle, oTitle, titles = $element[0].querySelectorAll('.title'); + + var newTitle = data.title; + if(!oldTitle || oldTitle === newTitle) { + cb(); + return; + } + + title = angular.element(titles[0]); + oTitle = $compile('

')($scope); + title.replaceWith(oTitle); + nTitle = $compile('

')($scope); + + var insert = $element[0].firstElementChild || null; + + $animate.enter(nTitle, $element, insert && angular.element(insert), function() { + cb(); + }); + $animate.leave(angular.element(oTitle), function() { + }); + + $scope.$on('navRouter.rightButtonsChanged', function(e, buttons) { + console.log('Buttons changing for nav bar', buttons); + }); + }; + + return { + restrict: 'E', + require: '^navRouter', + replace: true, + scope: { + type: '@', + backButtonType: '@', + backButtonLabel: '@', + backButtonIcon: '@', + alignTitle: '@', + }, + template: '', + link: function($scope, $element, $attr, navCtrl) { + var backButton; + + // Create the back button content and show/hide it based on scope settings + $scope.enableBackButton = true; + $scope.backButtonContent = ''; + if($scope.backButtonIcon) { + $scope.backButtonContent += ''; + } + if($scope.backButtonLabel) { + $scope.backButtonContent += ' ' + $scope.backButtonLabel + } + + // Listen for changes in the stack cursor position to indicate whether a back + // button should be shown (this can still be disabled by the $scope.enableBackButton + $rootScope.$watch('stackCursorPosition', function(value) { + if(value > 0) { + $scope.showBackButton = true; + } else { + $scope.showBackButton = false; + } + }); + + // Store a reference to our nav controller + $scope.navController = navCtrl; + + // Initialize our header bar view which will handle resizing and aligning our title labels + var hb = new ionic.views.HeaderBar({ + el: $element[0], + alignTitle: $scope.alignTitle || 'center' + }); + $scope.headerBarView = hb; + + // Add the type of header bar class to this element + $element.addClass($scope.type); + + var updateHeaderData = function(data) { + console.log('Header data changed', data); + + var oldTitle = $scope.currentTitle; + $scope.oldTitle = oldTitle; + + if(typeof data.title !== 'undefined') { + $scope.currentTitle = data.title; + } + + if(typeof data.leftButtons !== 'undefined') { + $scope.leftButtons = data.leftButtons; + } + if(typeof data.rightButtons !== 'undefined') { + $scope.rightButtons = data.rightButtons; + } + if(typeof data.hideBackButton !== 'undefined') { + $scope.enableBackButton = data.hideBackButton !== true; + } + + if(data.animate !== false && typeof data.title !== 'undefined') { + animate($scope, $element, oldTitle, data, function() { + hb.align(); + }); + } else { + hb.align(); + } + }; + + // Listen for changes on title change, and update the title + $scope.$parent.$on('navRouter.pageChanged', function(e, data) { + updateHeaderData(data); + }); + + $scope.$parent.$on('navRouter.pageShown', function(e, data) { + updateHeaderData(data); + }); + + + /* + $scope.$parent.$on('navigation.push', function() { + backButton = angular.element($element[0].querySelector('.button')); + backButton.addClass($scope.backButtonType); + hb.align(); + }); + $scope.$parent.$on('navigation.pop', function() { + hb.align(); + }); + */ + + $scope.$on('$destroy', function() { + // + }); + } + }; +}]) + +.directive('navPage', ['$parse', function($parse) { + return { + restrict: 'E', + scope: true, + require: '^navRouter', + link: function($scope, $element, $attr, navCtrl) { + $element.addClass('pane'); + + $scope.icon = $attr.icon; + $scope.iconOn = $attr.iconOn; + $scope.iconOff = $attr.iconOff; + + // Should we hide a back button when this tab is shown + $scope.hideBackButton = $scope.$eval($attr.hideBackButton); + + // Whether we should animate on tab change, also impacts whether we + // tell any parent nav controller to animate + $scope.animate = $scope.$eval($attr.animate); + + // Grab whether we should update any parent nav router on tab changes + $scope.doesUpdateNavRouter = $scope.$eval($attr.doesUpdateNavRouter) || true; + + // watch for changes in the left buttons + var leftButtonsGet = $parse($attr.leftButtons); + $scope.$watch(leftButtonsGet, function(value) { + $scope.leftButtons = value; + if($scope.doesUpdateNavRouter) { + $scope.$emit('navRouter.leftButtonsChanged', $scope.rightButtons); + } + }); + + // watch for changes in the right buttons + var rightButtonsGet = $parse($attr.rightButtons); + $scope.$watch(rightButtonsGet, function(value) { + $scope.rightButtons = value; + }); + + // watch for changes in the title + var titleGet = $parse($attr.title); + $scope.$watch(titleGet, function(value) { + $scope.title = value; + $scope.$emit('navRouter.pageChanged', { + title: value, + animate: $scope.animate + }); + }); + + } + } +}]) + +.directive('navBack', ['$window', '$rootScope', function($window, $rootScope) { + return { + restrict: 'AC', + require: '^?navRouter', + link: function($scope, $element, $attr, navCtrl) { + var goBack = function() { + // Only trigger back if the stack is greater than zero + if($rootScope.stackCursorPosition > 0) { + $window.history.back(); + } + }; + $element.bind('tap', goBack); + $element.bind('click', goBack); + + $scope.$on('$destroy', function() { + $element.unbind('tap', goBack); + $element.unbind('click', goBack); + }); + } + } +}]); + +})(); diff --git a/js/ext/angular/src/directive/ionicTabBar.js b/js/ext/angular/src/directive/ionicTabBar.js index c0f647d5c0..a415ad8fae 100644 --- a/js/ext/angular/src/directive/ionicTabBar.js +++ b/js/ext/angular/src/directive/ionicTabBar.js @@ -85,7 +85,7 @@ angular.module('ionic.ui.tabs', ['ngAnimate']) }) // Generic controller directive -.directive('tab', ['$animate', function($animate) { +.directive('tab', ['$animate', '$parse', function($animate, $parse) { return { restrict: 'E', replace: true, @@ -100,6 +100,30 @@ angular.module('ionic.ui.tabs', ['ngAnimate']) $scope.icon = $attr.icon; $scope.iconOn = $attr.iconOn; $scope.iconOff = $attr.iconOff; + + // Should we hide a back button when this tab is shown + $scope.hideBackButton = $scope.$eval($attr.hideBackButton); + + // Whether we should animate on tab change, also impacts whether we + // tell any parent nav controller to animate + $scope.animate = $scope.$eval($attr.animate); + + // Grab whether we should update any parent nav router on tab changes + $scope.doesUpdateNavRouter = $scope.$eval($attr.doesUpdateNavRouter) || true; + + var leftButtonsGet = $parse($attr.leftButtons); + $scope.$watch(leftButtonsGet, function(value) { + $scope.leftButtons = value; + if($scope.doesUpdateNavRouter) { + $scope.$emit('navRouter.leftButtonsChanged', $scope.rightButtons); + } + }); + + var rightButtonsGet = $parse($attr.rightButtons); + $scope.$watch(rightButtonsGet, function(value) { + $scope.rightButtons = value; + }); + tabsCtrl.add($scope); $scope.$watch('isVisible', function(value) { @@ -119,6 +143,19 @@ angular.module('ionic.ui.tabs', ['ngAnimate']) childElement.addClass('view-full'); $animate.enter(clone, $element.parent(), $element); + if($scope.title) { + // Send the title up in case we are inside of a nav controller + if($scope.doesUpdateNavRouter) { + $scope.$emit('navRouter.pageShown', { + title: $scope.title, + rightButtons: $scope.rightButtons, + leftButtons: $scope.leftButtons, + hideBackButton: $scope.hideBackButton || false, + animate: $scope.animate || true + }); + } + //$scope.$emit('navRouter.titleChanged', $scope.title); + } $scope.$broadcast('tab.shown'); }); } diff --git a/js/ext/angular/src/ionicAngular.js b/js/ext/angular/src/ionicAngular.js index 6cc73e061e..c94e8e8f8d 100644 --- a/js/ext/angular/src/ionicAngular.js +++ b/js/ext/angular/src/ionicAngular.js @@ -15,7 +15,7 @@ angular.module('ionic.service', [ angular.module('ionic.ui', [ 'ionic.ui.content', 'ionic.ui.tabs', - 'ionic.ui.nav', + 'ionic.ui.navRouter', 'ionic.ui.header', 'ionic.ui.sideMenu', 'ionic.ui.slideBox', @@ -31,6 +31,7 @@ angular.module('ionic', [ // Angular deps 'ngAnimate', + 'ngRoute', 'ngTouch', 'ngSanitize' ]); diff --git a/js/ext/angular/test/directive/ionicNavRouter.unit.js b/js/ext/angular/test/directive/ionicNavRouter.unit.js new file mode 100644 index 0000000000..2ae205c477 --- /dev/null +++ b/js/ext/angular/test/directive/ionicNavRouter.unit.js @@ -0,0 +1,19 @@ +describe('Angular Ionic Nav Router', function() { + var modal, q; + + beforeEach(module('ngRoute')); + beforeEach(module('ionic.ui.navRouter')); + + beforeEach(inject(function($compile, $rootScope, $controller) { + compile = $compile; + scope = $rootScope; + })); + + iit('Should init', function() { + element = compile('' + + '' + + '' + + '')(scope); + + }); +}); diff --git a/js/ext/angular/test/navAndTabs.html b/js/ext/angular/test/navAndTabs.html new file mode 100644 index 0000000000..ced699a4cd --- /dev/null +++ b/js/ext/angular/test/navAndTabs.html @@ -0,0 +1,149 @@ + + + + Nav Bars And Tabs + + + + + + + + + + + + + + + + + + + + + diff --git a/js/ext/angular/test/navRouter.html b/js/ext/angular/test/navRouter.html new file mode 100644 index 0000000000..3efa0238b2 --- /dev/null +++ b/js/ext/angular/test/navRouter.html @@ -0,0 +1,70 @@ + + + + Nav Bars + + + + + + + + + + + + + + + + + + + + diff --git a/scss/_animations.scss b/scss/_animations.scss index 77ff8b6ad3..e0431c3a84 100644 --- a/scss/_animations.scss +++ b/scss/_animations.scss @@ -1,3 +1,4 @@ +$ios7-timing-function: cubic-bezier(.1, .7, .1, 1); /** * Animations @@ -7,6 +8,107 @@ * and enhanced easily. */ +.noop-animation { + > .ng-enter, &.ng-enter, > .ng-leave, &.ng-leave { + position:absolute; + top:0; + left:0; + right:0; + bottom:0; + -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 250ms; + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 250ms; + } +} + +.slide-left-right { + > .ng-enter, &.ng-enter, > .ng-leave, &.ng-leave { + position:absolute; + top:0; + left:0; + right:0; + bottom:0; + -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 250ms; + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 250ms; + } + > .ng-enter, &.ng-enter { + -webkit-transform: translate3d(100%, 0, 0); + } + > .ng-enter.ng-enter-active, &.ng-enter.ng-enter-active { + -webkit-transform: translate3d(0, 0, 0); + } + > .ng-leave.ng-leave-active, &.ng-leave.ng-leave-active { + -webkit-transform: translate3d(-100%, 0, 0); + } + + &.reverse { + > .ng-enter, &.ng-enter, > .ng-leave, &.ng-leave { + position:absolute; + top:0; + left:0; + right:0; + bottom:0; + -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 250ms; + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 250ms; + } + > .ng-enter, &.ng-enter { + -webkit-transform: translate3d(-100%, 0, 0); + } + > .ng-enter.ng-enter-active, &.ng-enter.ng-enter-active { + -webkit-transform: translate3d(0, 0, 0); + } + > .ng-leave.ng-leave-active, &.ng-leave.ng-leave-active { + -webkit-transform: translate3d(100%, 0, 0); + } + } +} +.slide-left-right-ios7 { + > .ng-enter, &.ng-enter, > .ng-leave, &.ng-leave { + position:absolute; + top:0; + left:0; + right:0; + bottom:0; + -webkit-transition:all 250ms; + -webkit-transition-timing-function: $ios7-timing-function; + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 250ms; + transition:all 250ms; + transition-timing-function: $ios7-timing-function; + box-shadow: $menu-side-shadow; + } + > .ng-enter, &.ng-enter { + -webkit-transform: translate3d(100%, 0, 0); + } + > .ng-enter.ng-enter-active, &.ng-enter.ng-enter-active { + -webkit-transform: translate3d(0, 0, 0); + } + > .ng-leave.ng-leave-active, &.ng-leave.ng-leave-active { + -webkit-transform: translate3d(-20%, 0, 0); + } + + &.reverse { + > .ng-enter, &.ng-enter, > .ng-leave, &.ng-leave { + position:absolute; + top:0; + left:0; + right:0; + bottom:0; + -webkit-transition:all 250ms; + -webkit-transition-timing-function: $ios7-timing-function; + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 250ms; + transition-timing-function: $ios7-timing-function; + } + > .ng-enter, &.ng-enter { + -webkit-transform: translate3d(-20%, 0, 0); + } + > .ng-enter.ng-enter-active, &.ng-enter.ng-enter-active { + -webkit-transform: translate3d(0, 0, 0); + } + > .ng-leave.ng-leave-active, &.ng-leave.ng-leave-active { + -webkit-transform: translate3d(100%, 0, 0); + } + } +} + @-webkit-keyframes slideInLeft { 0% { -webkit-transform: translate3d(100%, 0, 0); @@ -272,3 +374,55 @@ $slide-in-up-function: cubic-bezier(.1, .7, .1, 1); } } + + +/** + * Some component specific animations + */ + +.nav-title-slide-ios7 { + > .ng-enter, &.ng-enter, > .ng-leave, &.ng-leave { + -webkit-transition:all 250ms; + -webkit-transition-timing-function: $ios7-timing-function; + transition:all 250ms; + transition-timing-function: $ios7-timing-function; + opacity: 1; + } + > .ng-enter, &.ng-enter { + opacity: 0; + -webkit-transform: translate3d(30%, 0, 0); + } + > .ng-enter.ng-enter-active, &.ng-enter.ng-enter-active { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + } + > .ng-leave.ng-leave-active, &.ng-leave.ng-leave-active { + opacity: 0; + -webkit-transform: translate3d(-30%, 0, 0); + } + +} +.reverse { + .nav-title-slide-ios7 { + > .ng-enter, &.ng-enter, > .ng-leave, &.ng-leave { + -webkit-transition:all 250ms; + -webkit-transition-timing-function: $ios7-timing-function; + transition:all 250ms; + transition-timing-function: $ios7-timing-function; + opacity: 1; + } + > .ng-enter, &.ng-enter { + opacity: 0; + -webkit-transform: translate3d(-30%, 0, 0); + } + > .ng-enter.ng-enter-active, &.ng-enter.ng-enter-active { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + } + > .ng-leave.ng-leave-active, &.ng-leave.ng-leave-active { + opacity: 0; + -webkit-transform: translate3d(30%, 0, 0); + } + + } +}