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);
+ }
+
+ }
+}