From 56a64b8bc67a3880330129849b0e4c62c7e2a488 Mon Sep 17 00:00:00 2001 From: Max Lynch Date: Mon, 11 Nov 2013 21:18:48 -0600 Subject: [PATCH] Fixed #115 and #116 --- dist/css/ionic.css | 6 +-- dist/js/ionic-angular.js | 59 ++++++++++++++---------- dist/js/ionic.js | 59 ++++++++++++++++++++---- js/controllers/navController.js | 14 ++---- js/ext/angular/src/directive/ionicNav.js | 59 ++++++++++++++---------- js/utils/utils.js | 45 +++++++++++++++++- scss/_animations.scss | 6 +-- 7 files changed, 172 insertions(+), 76 deletions(-) diff --git a/dist/css/ionic.css b/dist/css/ionic.css index 9ec0a586a5..7b4023f373 100644 --- a/dist/css/ionic.css +++ b/dist/css/ionic.css @@ -4602,7 +4602,7 @@ a.button { @-webkit-keyframes slideOutRight { 0% { - -webkit-transform: translate3d(0%, 0, 0); } + -webkit-transform: translate3d(0, 0, 0); } 100% { -webkit-transform: translate3d(100%, 0, 0); } } @@ -4641,12 +4641,12 @@ a.button { -webkit-animation-name: slideOutRight; } .slide-out-right.ng-enter, .slide-out-right > .ng-enter { - -webkit-animation-duration: 2250ms; + -webkit-animation-duration: 250ms; -webkit-animation-fill-mode: both; -webkit-animation-timing-function: ease-in-out; -webkit-animation-name: slideOutRight; } .slide-out-right.ng-leave, .slide-out-right > .ng-leave { - -webkit-animation-duration: 2250ms; + -webkit-animation-duration: 250ms; -webkit-animation-fill-mode: both; -webkit-animation-timing-function: ease-in-out; -webkit-animation-name: slideInRight; } diff --git a/dist/js/ionic-angular.js b/dist/js/ionic-angular.js index c6b5bf0b1b..70d4024043 100644 --- a/dist/js/ionic-angular.js +++ b/dist/js/ionic-angular.js @@ -788,7 +788,7 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges * Push a template onto the navigation stack. * @param {string} templateUrl the URL of the template to load. */ - this.pushFromTemplate = function(templateUrl) { + this.pushFromTemplate = ionic.debounce(function(templateUrl) { var childScope = $scope.$new(); childScope.isVisible = true; @@ -798,13 +798,10 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges // Compile the template with the new scrope, and append it to the navigation's content area var el = $compile(templateString)(childScope, function(cloned, scope) { var content = angular.element($element[0].querySelector('.content, .scroll')); - - //content.append(cloned); - //angular.element(content).append(cloned); $animate.enter(cloned, angular.element(content)); }); }); - }; + }, 100, true); /** * Push a controller to the stack. This is called by the child @@ -909,7 +906,7 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges // Store that we should go forwards on the animation. This toggles // based on the visibility sequence (to support reverse transitions) - var wasVisible = null; + var lastDirection = null; $scope.title = $attr.title; $scope.pushAnimation = $attr.pushAnimation || 'slide-in-left'; @@ -925,6 +922,24 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges navCtrl.showNavBar(); } + $scope.visibilityChanged = function(direction) { + lastDirection = direction; + + if(!childElement) { + return; + } + + var clone = childElement; + + if(direction == 'push') { + clone.addClass(childScope.pushAnimation); + clone.removeClass(childScope.popAnimation); + } else if(direction == 'pop') { + clone.addClass(childScope.popAnimation); + clone.removeClass(childScope.pushAnimation); + } + }; + // Push this controller onto the stack $scope.pushController($scope, $element); @@ -936,39 +951,33 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges transclude(childScope, function(clone) { childElement = clone; - // Check if this is visible, and if so, create it and show it - if(wasVisible === false) { - clone.removeClass(childScope.pushAnimation); - clone.addClass(childScope.popAnimation); - } else { + if(lastDirection == 'push') { clone.addClass(childScope.pushAnimation); - clone.removeClass(childScope.popAnimation); + } else if(lastDirection == 'pop') { + clone.addClass(childScope.popAnimation); } - $animate.enter(clone, $element.parent(), $element); - wasVisible = true; + $animate.enter(clone, $element.parent(), $element, function() { + clone.removeClass(childScope.pushAnimation); + clone.removeClass(childScope.popAnimation); + }); }); } else { // Taken from ngIf if(childElement) { - var clone = childElement; // Check if this is visible, and if so, create it and show it - if(wasVisible === false) { - clone.removeClass(childScope.pushAnimation); - clone.addClass(childScope.popAnimation); - } else { - clone.addClass(childScope.pushAnimation); - clone.removeClass(childScope.popAnimation); - } - $animate.leave(childElement); + $animate.leave(childElement, function() { + if(childScope) { + childElement.removeClass(childScope.pushAnimation); + childElement.removeClass(childScope.popAnimation); + } + }); childElement = undefined; - wasVisible = false; } if(childScope) { childScope.$destroy(); childScope = undefined; } - } }); } diff --git a/dist/js/ionic.js b/dist/js/ionic.js index 13271d85df..947a94df62 100644 --- a/dist/js/ionic.js +++ b/dist/js/ionic.js @@ -1837,9 +1837,16 @@ window.ionic = { ; (function(ionic) { + /** + * Various utilities used throughout Ionic + * + * Some of these are adopted from underscore.js and backbone.js, both also MIT licensed. + */ ionic.Utils = { - // Return a function that will be called with the given context + /** + * Return a function that will be called with the given context + */ proxy: function(func, context) { var args = Array.prototype.slice.call(arguments, 2); return function() { @@ -1847,6 +1854,41 @@ window.ionic = { }; }, + /** + * Only call a function once in the given interval. + * + * @param func {Function} the function to call + * @param wait {int} how long to wait before/after to allow function calls + * @param immediate {boolean} whether to call immediately or after the wait interval + */ + debounce: function(func, wait, immediate) { + var timeout, args, context, timestamp, result; + return function() { + context = this; + args = arguments; + timestamp = new Date(); + var later = function() { + var last = (new Date()) - timestamp; + if (last < wait) { + timeout = setTimeout(later, wait - last); + } else { + timeout = null; + if (!immediate) result = func.apply(context, args); + } + }; + var callNow = immediate && !timeout; + if (!timeout) { + timeout = setTimeout(later, wait); + } + if (callNow) result = func.apply(context, args); + return result; + }; + }, + + /** + * Throttle the given fun, only allowing it to be + * called at most every `wait` ms. + */ throttle: function(func, wait, options) { var context, args, result; var timeout = null; @@ -1931,6 +1973,7 @@ window.ionic = { ionic.extend = ionic.Utils.extend; ionic.throttle = ionic.Utils.throttle; ionic.proxy = ionic.Utils.proxy; + ionic.debounce = ionic.Utils.debounce; })(window.ionic); ; @@ -4188,21 +4231,17 @@ ionic.controllers.NavController = ionic.controllers.ViewController.inherit({ return; // Actually switch the active controllers - - // Remove the old one - //last && last.detach(); if(last) { last.isVisible = false; - last.visibilityChanged && last.visibilityChanged(); + last.visibilityChanged && last.visibilityChanged('push'); } // Grab the top controller on the stack var next = this.controllers[this.controllers.length - 1]; - // TODO: No DOM stuff here - //this.content.el.appendChild(next.el); next.isVisible = true; - next.visibilityChanged && next.visibilityChanged(); + // Trigger visibility change, but send 'first' if this is the first page + next.visibilityChanged && next.visibilityChanged(last ? 'push' : 'first'); this._updateNavBar(); @@ -4227,7 +4266,7 @@ ionic.controllers.NavController = ionic.controllers.ViewController.inherit({ last = this.controllers.pop(); if(last) { last.isVisible = false; - last.visibilityChanged && last.visibilityChanged(); + last.visibilityChanged && last.visibilityChanged('pop'); } // Remove the old one @@ -4238,7 +4277,7 @@ ionic.controllers.NavController = ionic.controllers.ViewController.inherit({ // TODO: No DOM stuff here //this.content.el.appendChild(next.el); next.isVisible = true; - next.visibilityChanged && next.visibilityChanged(); + next.visibilityChanged && next.visibilityChanged('pop'); // Switch to it (TODO: Animate or such things here) diff --git a/js/controllers/navController.js b/js/controllers/navController.js index b56b7f9d97..29460f5670 100644 --- a/js/controllers/navController.js +++ b/js/controllers/navController.js @@ -60,21 +60,17 @@ ionic.controllers.NavController = ionic.controllers.ViewController.inherit({ return; // Actually switch the active controllers - - // Remove the old one - //last && last.detach(); if(last) { last.isVisible = false; - last.visibilityChanged && last.visibilityChanged(); + last.visibilityChanged && last.visibilityChanged('push'); } // Grab the top controller on the stack var next = this.controllers[this.controllers.length - 1]; - // TODO: No DOM stuff here - //this.content.el.appendChild(next.el); next.isVisible = true; - next.visibilityChanged && next.visibilityChanged(); + // Trigger visibility change, but send 'first' if this is the first page + next.visibilityChanged && next.visibilityChanged(last ? 'push' : 'first'); this._updateNavBar(); @@ -99,7 +95,7 @@ ionic.controllers.NavController = ionic.controllers.ViewController.inherit({ last = this.controllers.pop(); if(last) { last.isVisible = false; - last.visibilityChanged && last.visibilityChanged(); + last.visibilityChanged && last.visibilityChanged('pop'); } // Remove the old one @@ -110,7 +106,7 @@ ionic.controllers.NavController = ionic.controllers.ViewController.inherit({ // TODO: No DOM stuff here //this.content.el.appendChild(next.el); next.isVisible = true; - next.visibilityChanged && next.visibilityChanged(); + next.visibilityChanged && next.visibilityChanged('pop'); // Switch to it (TODO: Animate or such things here) diff --git a/js/ext/angular/src/directive/ionicNav.js b/js/ext/angular/src/directive/ionicNav.js index 84f284b4b4..551ec9cff1 100644 --- a/js/ext/angular/src/directive/ionicNav.js +++ b/js/ext/angular/src/directive/ionicNav.js @@ -48,7 +48,7 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges * Push a template onto the navigation stack. * @param {string} templateUrl the URL of the template to load. */ - this.pushFromTemplate = function(templateUrl) { + this.pushFromTemplate = ionic.debounce(function(templateUrl) { var childScope = $scope.$new(); childScope.isVisible = true; @@ -58,13 +58,10 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges // Compile the template with the new scrope, and append it to the navigation's content area var el = $compile(templateString)(childScope, function(cloned, scope) { var content = angular.element($element[0].querySelector('.content, .scroll')); - - //content.append(cloned); - //angular.element(content).append(cloned); $animate.enter(cloned, angular.element(content)); }); }); - }; + }, 100, true); /** * Push a controller to the stack. This is called by the child @@ -169,7 +166,7 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges // Store that we should go forwards on the animation. This toggles // based on the visibility sequence (to support reverse transitions) - var wasVisible = null; + var lastDirection = null; $scope.title = $attr.title; $scope.pushAnimation = $attr.pushAnimation || 'slide-in-left'; @@ -185,6 +182,24 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges navCtrl.showNavBar(); } + $scope.visibilityChanged = function(direction) { + lastDirection = direction; + + if(!childElement) { + return; + } + + var clone = childElement; + + if(direction == 'push') { + clone.addClass(childScope.pushAnimation); + clone.removeClass(childScope.popAnimation); + } else if(direction == 'pop') { + clone.addClass(childScope.popAnimation); + clone.removeClass(childScope.pushAnimation); + } + }; + // Push this controller onto the stack $scope.pushController($scope, $element); @@ -196,39 +211,33 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges transclude(childScope, function(clone) { childElement = clone; - // Check if this is visible, and if so, create it and show it - if(wasVisible === false) { - clone.removeClass(childScope.pushAnimation); - clone.addClass(childScope.popAnimation); - } else { + if(lastDirection == 'push') { clone.addClass(childScope.pushAnimation); - clone.removeClass(childScope.popAnimation); + } else if(lastDirection == 'pop') { + clone.addClass(childScope.popAnimation); } - $animate.enter(clone, $element.parent(), $element); - wasVisible = true; + $animate.enter(clone, $element.parent(), $element, function() { + clone.removeClass(childScope.pushAnimation); + clone.removeClass(childScope.popAnimation); + }); }); } else { // Taken from ngIf if(childElement) { - var clone = childElement; // Check if this is visible, and if so, create it and show it - if(wasVisible === false) { - clone.removeClass(childScope.pushAnimation); - clone.addClass(childScope.popAnimation); - } else { - clone.addClass(childScope.pushAnimation); - clone.removeClass(childScope.popAnimation); - } - $animate.leave(childElement); + $animate.leave(childElement, function() { + if(childScope) { + childElement.removeClass(childScope.pushAnimation); + childElement.removeClass(childScope.popAnimation); + } + }); childElement = undefined; - wasVisible = false; } if(childScope) { childScope.$destroy(); childScope = undefined; } - } }); } diff --git a/js/utils/utils.js b/js/utils/utils.js index 7ed13215e0..dd817b60a4 100644 --- a/js/utils/utils.js +++ b/js/utils/utils.js @@ -1,8 +1,15 @@ (function(ionic) { + /** + * Various utilities used throughout Ionic + * + * Some of these are adopted from underscore.js and backbone.js, both also MIT licensed. + */ ionic.Utils = { - // Return a function that will be called with the given context + /** + * Return a function that will be called with the given context + */ proxy: function(func, context) { var args = Array.prototype.slice.call(arguments, 2); return function() { @@ -10,6 +17,41 @@ }; }, + /** + * Only call a function once in the given interval. + * + * @param func {Function} the function to call + * @param wait {int} how long to wait before/after to allow function calls + * @param immediate {boolean} whether to call immediately or after the wait interval + */ + debounce: function(func, wait, immediate) { + var timeout, args, context, timestamp, result; + return function() { + context = this; + args = arguments; + timestamp = new Date(); + var later = function() { + var last = (new Date()) - timestamp; + if (last < wait) { + timeout = setTimeout(later, wait - last); + } else { + timeout = null; + if (!immediate) result = func.apply(context, args); + } + }; + var callNow = immediate && !timeout; + if (!timeout) { + timeout = setTimeout(later, wait); + } + if (callNow) result = func.apply(context, args); + return result; + }; + }, + + /** + * Throttle the given fun, only allowing it to be + * called at most every `wait` ms. + */ throttle: function(func, wait, options) { var context, args, result; var timeout = null; @@ -94,5 +136,6 @@ ionic.extend = ionic.Utils.extend; ionic.throttle = ionic.Utils.throttle; ionic.proxy = ionic.Utils.proxy; + ionic.debounce = ionic.Utils.debounce; })(window.ionic); diff --git a/scss/_animations.scss b/scss/_animations.scss index ea68d35243..72f4b60d03 100644 --- a/scss/_animations.scss +++ b/scss/_animations.scss @@ -33,7 +33,7 @@ } @-webkit-keyframes slideOutRight { 0% { - -webkit-transform: translate3d(0%,0,0); + -webkit-transform: translate3d(0,0,0); } 100% { -webkit-transform: translate3d(100%,0,0); @@ -87,13 +87,13 @@ .slide-out-right { &.ng-enter, > .ng-enter { - -webkit-animation-duration: 2250ms; + -webkit-animation-duration: 250ms; -webkit-animation-fill-mode: both; -webkit-animation-timing-function: ease-in-out; -webkit-animation-name: slideOutRight; } &.ng-leave, > .ng-leave { - -webkit-animation-duration: 2250ms; + -webkit-animation-duration: 250ms; -webkit-animation-fill-mode: both; -webkit-animation-timing-function: ease-in-out; -webkit-animation-name: slideInRight;