diff --git a/dist/css/ionic.css b/dist/css/ionic.css index 722974e528..139675c54b 100644 --- a/dist/css/ionic.css +++ b/dist/css/ionic.css @@ -2282,8 +2282,37 @@ body, .ionic-body { text-align: center; position: relative; } +.ionic-refresher-content { + height: 100%; + width: 100%; + text-align: center; + position: relative; } + +.ionic-refresher { + width: 10px; + height: 10px; + border-radius: 50%; + background-color: #4a87ee; + position: absolute; + left: 50%; + margin-left: -5px; + bottom: 25px; } + .scroll-refreshing { -webkit-transition: height 0.1s ease-in-out; } + .scroll-refreshing .ionic-refresher { + -webkit-animation: refresher-pulsate 1.5s linear; + -webkit-animation-iteration-count: infinite; } + +@-webkit-keyframes refresher-pulsate { + 0% { + -webkit-transform: scale(2, 2); } + + 50% { + -webkit-transform: scale(1.5, 1.5); } + + 100% { + -webkit-transform: scale(2, 2); } } .overflow-scroll { overflow: auto; @@ -4936,7 +4965,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); } } @@ -4975,12 +5004,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 11199ec5a0..9b83827c3f 100644 --- a/dist/js/ionic-angular.js +++ b/dist/js/ionic-angular.js @@ -548,11 +548,39 @@ angular.module('ionic.ui.content', []) return { restrict: 'E', replace: true, - transclude: true, - template: '
' + require: ['^?content', '^?list'], + template: '
', + scope: true, + link: function($scope, $element, $attr, scrollCtrl) { + var icon = $element[0].querySelector('.ionic-refresher'); + + // Scale up the refreshing icon + var onRefreshOpening = ionic.throttle(function(e, amt) { + icon.style[ionic.CSS.TRANSFORM] = 'scale(' + Math.min((1 + amt), 2) + ')'; + }, 100); + + $scope.$on('onRefreshing', function(e) { + icon.style[ionic.CSS.TRANSFORM] = 'scale(2)'; + }); + + $scope.$on('onRefresh', function(e) { + icon.style[ionic.CSS.TRANSFORM] = 'scale(1)'; + }); + $scope.$on('onRefreshOpening', onRefreshOpening); + } } }) +.directive('scroll-refresher', function() { + return { + restrict: 'E', + replace: true, + transclude: true, + template: '
' + } +}); + + })(); ; (function() { @@ -560,10 +588,10 @@ angular.module('ionic.ui.content', []) angular.module('ionic.ui.list', ['ngAnimate']) -.directive('listItem', ['$timeout', function($timeout) { +.directive('item', ['$timeout', function($timeout) { return { restrict: 'E', - require: ['?^list', '?^virtualList'], + require: ['?^list'], replace: true, transclude: true, scope: { @@ -574,8 +602,9 @@ angular.module('ionic.ui.list', ['ngAnimate']) canReorder: '@', canSwipe: '@', buttons: '=', + type: '@' }, - template: '\ + template: '\
\ \
\ @@ -589,20 +618,6 @@ angular.module('ionic.ui.list', ['ngAnimate']) \
', - /* - template: '
  • \ -
    \ - \ -
    \ -
    \ -
    \ -
    \ - \ -
    \ -
    \ - \ -
    \ -
  • ',*/ link: function($scope, $element, $attr, list) { // Grab the parent list controller if(list[0]) { @@ -611,6 +626,13 @@ angular.module('ionic.ui.list', ['ngAnimate']) list = list[1]; } + // Add the list item type class + $element.addClass($attr.type || 'item-slider'); + + if($attr.type !== 'item-slider') { + $scope.canSwipe = false; + } + $scope.isEditing = false; $scope.deleteIcon = list.scope.deleteIcon; $scope.reorderIcon = list.scope.reorderIcon; @@ -647,6 +669,7 @@ angular.module('ionic.ui.list', ['ngAnimate']) isEditing: '=', deleteIcon: '@', reorderIcon: '@', + hasPullToRefresh: '@', onRefresh: '&', onRefreshOpening: '&' }, @@ -669,12 +692,14 @@ angular.module('ionic.ui.list', ['ngAnimate']) var lv = new ionic.views.ListView({ el: $element[0], listEl: $element[0].children[0], - hasPullToRefresh: (typeof $scope.onRefresh !== 'undefined'), + hasPullToRefresh: ($scope.hasPullToRefresh !== 'false'), onRefresh: function() { $scope.onRefresh(); + $scope.$parent.$broadcast('onRefresh'); }, onRefreshOpening: function(amt) { $scope.onRefreshOpening({amount: amt}); + $scope.$parent.$broadcast('onRefreshOpening', amt); } }); @@ -723,6 +748,31 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges angular.extend(this, ionic.controllers.NavController.prototype); + /** + * Push a template onto the navigation stack. + * @param {string} templateUrl the URL of the template to load. + */ + this.pushFromTemplate = ionic.debounce(function(templateUrl) { + var childScope = $scope.$new(); + childScope.isVisible = true; + + // Load the given template + TemplateLoader.load(templateUrl).then(function(templateString) { + + // 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')); + $animate.enter(cloned, angular.element(content)); + }); + }); + }, 300, true); + + // Pop function, debounced + this.popController = ionic.debounce(function() { + _this.pop(); + }, 300, true); + + ionic.controllers.NavController.call(this, { content: { }, @@ -746,7 +796,7 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges // Support Android hardware back button (native only, not mobile web) var onHardwareBackButton = function(e) { $scope.$apply(function() { - _this.pop(); + _this.popController(); }); } Platform.onHardwareBackButton(onHardwareBackButton); @@ -759,28 +809,6 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges this.endDrag = function(e) { }; - /** - * Push a template onto the navigation stack. - * @param {string} templateUrl the URL of the template to load. - */ - this.pushFromTemplate = function(templateUrl) { - var childScope = $scope.$new(); - childScope.isVisible = true; - - // Load the given template - TemplateLoader.load(templateUrl).then(function(templateString) { - - // 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')); - - //content.append(cloned); - //angular.element(content).append(cloned); - $animate.enter(cloned, angular.element(content)); - }); - }); - }; - /** * Push a controller to the stack. This is called by the child * nav-content directive when it is linked to a scope on the page. @@ -827,7 +855,7 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges scope.$watch('navController.controllers.length', function(value) { }); scope.goBack = function() { - navCtrl.pop(); + navCtrl.popController(); }; } }; @@ -884,7 +912,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'; @@ -900,6 +928,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); @@ -911,39 +957,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; } - } }); } @@ -975,27 +1015,18 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture']) angular.extend(this, ionic.controllers.SideMenuController.prototype); ionic.controllers.SideMenuController.call(this, { + // Our quick implementation of the left side menu left: { width: 270, - pushDown: function() { - $scope.leftZIndex = -1; - }, - bringUp: function() { - $scope.leftZIndex = 0; - } }, + + // Our quick implementation of the right side menu right: { width: 270, - pushDown: function() { - $scope.rightZIndex = -1; - }, - bringUp: function() { - $scope.rightZIndex = 0; - } } }); - $scope.contentTranslateX = 0; + $scope.sideMenuContentTranslateX = 0; $scope.sideMenuCtrl = this; }) @@ -1027,28 +1058,32 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture']) defaultPrevented = e.defaultPrevented; }); - Gesture.on('drag', function(e) { + var dragFn = function(e) { if(defaultPrevented) { return; } sideMenuCtrl._handleDrag(e); - }, $element[0]); + }; - Gesture.on('release', function(e) { + Gesture.on('drag', dragFn, $element[0]); + + var dragReleaseFn = function(e) { if(!defaultPrevented) { sideMenuCtrl._endDrag(e); } defaultPrevented = false; - }, $element[0]); + }; + + Gesture.on('release', dragReleaseFn, $element[0]); sideMenuCtrl.setContent({ onDrag: function(e) {}, endDrag: function(e) {}, getTranslateX: function() { - return $scope.contentTranslateX || 0; + return $scope.sideMenuContentTranslateX || 0; }, setTranslateX: function(amount) { - $scope.contentTranslateX = amount; + $scope.sideMenuContentTranslateX = amount; $element[0].style.webkitTransform = 'translate3d(' + amount + 'px, 0, 0)'; }, enableAnimation: function() { @@ -1062,6 +1097,12 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture']) $element[0].classList.remove('menu-animated'); } }); + + // Cleanup + $scope.$on('$destroy', function() { + Gesture.off('drag', dragFn); + Gesture.off('release', dragReleaseFn); + }); }; } }; @@ -1080,10 +1121,23 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture']) return function($scope, $element, $attr, sideMenuCtrl) { $scope.side = $attr.side; + if($scope.side == 'left') { sideMenuCtrl.left.isEnabled = true; + sideMenuCtrl.left.pushDown = function() { + $element[0].style.zIndex = -1; + }; + sideMenuCtrl.left.bringUp = function() { + $element[0].style.zIndex = 0; + }; } else if($scope.side == 'right') { sideMenuCtrl.right.isEnabled = true; + sideMenuCtrl.right.pushDown = function() { + $element[0].style.zIndex = -1; + }; + sideMenuCtrl.right.bringUp = function() { + $element[0].style.zIndex = 0; + }; } $element.append(transclude($scope)); diff --git a/dist/js/ionic.js b/dist/js/ionic.js index b12d4a9664..f5067571ea 100644 --- a/dist/js/ionic.js +++ b/dist/js/ionic.js @@ -8,6 +8,8 @@ http://ionicframework.com/ By @maxlynch, @helloimben, @adamdbradley <3 Licensed under the MIT license. Please see LICENSE for more information. + +Make awesome shit. */ ; @@ -1835,7 +1837,58 @@ 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 + */ + proxy: function(func, context) { + var args = Array.prototype.slice.call(arguments, 2); + return function() { + return func.apply(context, args.concat(Array.prototype.slice.call(arguments))); + }; + }, + + /** + * 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; @@ -1915,9 +1968,12 @@ window.ionic = { } }; + // Bind a few of the most useful functions to the ionic scope ionic.inherit = ionic.Utils.inherit; ionic.extend = ionic.Utils.extend; ionic.throttle = ionic.Utils.throttle; + ionic.proxy = ionic.Utils.proxy; + ionic.debounce = ionic.Utils.debounce; })(window.ionic); ; @@ -1977,7 +2033,7 @@ window.ionic = { dragThreshold: 10, // Resistance when scrolling too far up or down - rubberBandResistance: 3, + rubberBandResistance: 2, // Scroll event names. These are custom so can be configured scrollEventName: 'momentumScrolled', @@ -1985,6 +2041,10 @@ window.ionic = { hasPullToRefresh: true, + // Whether to disable overflow rubber banding when content is small + // enough to fit in the viewport (i.e. doesn't need scrolling) + disableNonOverflowRubberBand: false, + // Called as the refresher is opened, an amount is passed onRefreshOpening: function() {}, // Called when let go and is refreshing @@ -2178,6 +2238,10 @@ window.ionic = { } }, + /** + * Check if the current scroll bounds needs to be brought back to the min/max + * allowable given the total scrollable area. + */ needsWrapping: function() { var _this = this; @@ -2400,14 +2464,6 @@ window.ionic = { var parentWidth = this.el.parentNode.offsetWidth; var parentHeight = this.el.parentNode.offsetHeight; - var maxX = Math.min(0, (-totalWidth + parentWidth)); - var maxY = Math.min(0, (-totalHeight + parentHeight)); - - // Check if we even have enough content to scroll, if not, don't start the drag - if((this.isHorizontalEnabled && maxX == 0) || (this.isVerticalEnabled && maxY == 0)) { - return; - } - this.x = scrollLeft; this.y = scrollTop; @@ -2444,6 +2500,16 @@ window.ionic = { resist: 1, startTime: Date.now() }; + + if(this.disableNonOverflowRubberBand === true) { + var maxX = Math.min(0, (-totalWidth + parentWidth)); + var maxY = Math.min(0, (-totalHeight + parentHeight)); + + // Check if we even have enough content to scroll, if not, don't start the drag + if((this.isHorizontalEnabled && maxX == 0) || (this.isVerticalEnabled && maxY == 0)) { + this._drag.noRubberBand = true; + } + } }, /** @@ -2511,9 +2577,22 @@ window.ionic = { var newX = _this.x + deltaX; var newY = _this.y + deltaY; - // Check if the dragging is beyond the bottom or top - if(newY > 0 || (-newY + parentHeight) > totalHeight) { - newY = _this.y + deltaY / _this.rubberBandResistance; + if(drag.noRubberBand === true) { + if(newY > 0) { + newY = 0; + } else if(newY < maxY) { + newY = maxY; + } + if(newX > 0) { + newX = 0; + } else if(newX < maxX) { + newX = maxX; + } + } else { + // Check if the dragging is beyond the bottom or top + if(newY > 0 || (-newY + parentHeight) > totalHeight) { + newY = _this.y + deltaY / _this.rubberBandResistance; + } } if(!_this.isHorizontalEnabled) { @@ -2525,7 +2604,6 @@ window.ionic = { if(_this._refresher && newY > 0) { // We are pulling to refresh, so update the refresher - //_this._refresher.style[ionic.CSS.TRANSFORM] = 'translate3d(0, ' + newY + 'px, 0)'; if(_this._isRefresherHidden) { // Show it only in a drag and if we haven't showed it yet _this._refresher.style.display = 'block'; @@ -2542,7 +2620,7 @@ window.ionic = { } // Update the new translated Y point of the container - _this.el.style.webkitTransform = 'translate3d(' + newX + 'px,' + newY + 'px, 0)'; + _this.el.style[ionic.CSS.TRANSFORM] = 'translate3d(' + newX + 'px,' + newY + 'px, 0)'; } else { _this._isHoldingRefresh = false; @@ -2553,7 +2631,7 @@ window.ionic = { _this._isRefresherHidden = true; } // Update the new translated Y point of the container - _this.el.style.webkitTransform = 'translate3d(' + newX + 'px,' + newY + 'px, 0)'; + _this.el.style[ionic.CSS.TRANSFORM] = 'translate3d(' + newX + 'px,' + newY + 'px, 0)'; } // Store the last points @@ -3165,8 +3243,6 @@ window.ionic = { this._isDragging = false; - console.log(e.gesture.direction); - // Check if this is a reorder drag if(ionic.DomUtil.getParentOrSelfWithClass(e.target, ITEM_DRAG_CLASS) && (e.gesture.direction == 'up' || e.gesture.direction == 'down')) { var item = this._getItem(e.target); @@ -3467,6 +3543,39 @@ window.ionic = { } }); + ionic.views.SideMenuContent = ionic.views.View.inherit({ + initialize: function(opts) { + var _this = this; + + ionic.extend(this, { + animationClass: 'menu-animated', + onDrag: function(e) {}, + onEndDrag: function(e) {}, + }, opts); + + ionic.onGesture('drag', ionic.proxy(this._onDrag, this), this.el); + ionic.onGesture('release', ionic.proxy(this._onEndDrag, this), this.el); + }, + _onDrag: function(e) { + this.onDrag && this.onDrag(e); + }, + _onEndDrag: function(e) { + this.onEndDrag && this.onEndDrag(e); + }, + disableAnimation: function() { + this.el.classList.remove(this.animationClass); + }, + enableAnimation: function() { + this.el.classList.add(this.animationClass); + }, + getTranslateX: function() { + return parseFloat(this.el.style.webkitTransform.replace('translate3d(', '').split(',')[0]); + }, + setTranslateX: function(x) { + this.el.style.webkitTransform = 'translate3d(' + x + 'px, 0, 0)'; + } + }); + })(ionic); ; /** @@ -4142,21 +4251,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(); @@ -4181,7 +4286,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 @@ -4192,7 +4297,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) @@ -4264,7 +4369,7 @@ ionic.controllers.NavController = ionic.controllers.ViewController.inherit({ self._handleDrag(e); }; - this.content.endDrag = function(e) { + this.content.onEndDrag =function(e) { self._endDrag(e); }; } @@ -4354,12 +4459,12 @@ ionic.controllers.NavController = ionic.controllers.ViewController.inherit({ */ openPercentage: function(percentage) { var p = percentage / 100; - var maxLeft = this.left.width; - var maxRight = this.right.width; - if(percentage >= 0) { - this.openAmount(maxLeft * p); - } else { - this.openAmount(maxRight * p); + + if(this.left && percentage >= 0) { + this.openAmount(this.left.width * p); + } else if(this.right && percentage < 0) { + var maxRight = this.right.width; + this.openAmount(this.right.width * p); } }, @@ -4369,11 +4474,11 @@ ionic.controllers.NavController = ionic.controllers.ViewController.inherit({ * negative value for right menu (only one menu will be visible at a time). */ openAmount: function(amount) { - var maxLeft = this.left.width; - var maxRight = this.right.width; + var maxLeft = this.left && this.left.width || 0; + var maxRight = this.right && this.right.width || 0; // Check if we can move to that side, depending if the left/right panel is enabled - if((!this.left.isEnabled && amount > 0) || (!this.right.isEnabled && amount < 0)) { + if((!(this.left && this.left.isEnabled) && amount > 0) || (!(this.right && this.right.isEnabled) && amount < 0)) { return; } @@ -4388,17 +4493,17 @@ ionic.controllers.NavController = ionic.controllers.ViewController.inherit({ this._rightShowing = false; // Push the z-index of the right menu down - this.right.pushDown(); + this.right && this.right.pushDown(); // Bring the z-index of the left menu up - this.left.bringUp(); + this.left && this.left.bringUp(); } else { this._rightShowing = true; this._leftShowing = false; // Bring the z-index of the right menu up - this.right.bringUp(); + this.right && this.right.bringUp(); // Push the z-index of the left menu down - this.left.pushDown(); + this.left && this.left.pushDown(); } }, diff --git a/js/_license.js b/js/_license.js index bac744f1fb..6e4dc6a37b 100644 --- a/js/_license.js +++ b/js/_license.js @@ -8,4 +8,6 @@ http://ionicframework.com/ By @maxlynch, @helloimben, @adamdbradley <3 Licensed under the MIT license. Please see LICENSE for more information. + +Make awesome shit. */ 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/controllers/sideMenuController.js b/js/controllers/sideMenuController.js index e196d946ac..dd1aff5e15 100644 --- a/js/controllers/sideMenuController.js +++ b/js/controllers/sideMenuController.js @@ -25,7 +25,7 @@ self._handleDrag(e); }; - this.content.endDrag = function(e) { + this.content.onEndDrag =function(e) { self._endDrag(e); }; } @@ -115,12 +115,12 @@ */ openPercentage: function(percentage) { var p = percentage / 100; - var maxLeft = this.left.width; - var maxRight = this.right.width; - if(percentage >= 0) { - this.openAmount(maxLeft * p); - } else { - this.openAmount(maxRight * p); + + if(this.left && percentage >= 0) { + this.openAmount(this.left.width * p); + } else if(this.right && percentage < 0) { + var maxRight = this.right.width; + this.openAmount(this.right.width * p); } }, @@ -130,11 +130,11 @@ * negative value for right menu (only one menu will be visible at a time). */ openAmount: function(amount) { - var maxLeft = this.left.width; - var maxRight = this.right.width; + var maxLeft = this.left && this.left.width || 0; + var maxRight = this.right && this.right.width || 0; // Check if we can move to that side, depending if the left/right panel is enabled - if((!this.left.isEnabled && amount > 0) || (!this.right.isEnabled && amount < 0)) { + if((!(this.left && this.left.isEnabled) && amount > 0) || (!(this.right && this.right.isEnabled) && amount < 0)) { return; } @@ -149,17 +149,17 @@ this._rightShowing = false; // Push the z-index of the right menu down - this.right.pushDown(); + this.right && this.right.pushDown(); // Bring the z-index of the left menu up - this.left.bringUp(); + this.left && this.left.bringUp(); } else { this._rightShowing = true; this._leftShowing = false; // Bring the z-index of the right menu up - this.right.bringUp(); + this.right && this.right.bringUp(); // Push the z-index of the left menu down - this.left.pushDown(); + this.left && this.left.pushDown(); } }, diff --git a/js/ext/angular/src/directive/ionicContent.js b/js/ext/angular/src/directive/ionicContent.js index c15d895daf..97d9796083 100644 --- a/js/ext/angular/src/directive/ionicContent.js +++ b/js/ext/angular/src/directive/ionicContent.js @@ -76,9 +76,37 @@ angular.module('ionic.ui.content', []) return { restrict: 'E', replace: true, - transclude: true, - template: '
    ' + require: ['^?content', '^?list'], + template: '
    ', + scope: true, + link: function($scope, $element, $attr, scrollCtrl) { + var icon = $element[0].querySelector('.ionic-refresher'); + + // Scale up the refreshing icon + var onRefreshOpening = ionic.throttle(function(e, amt) { + icon.style[ionic.CSS.TRANSFORM] = 'scale(' + Math.min((1 + amt), 2) + ')'; + }, 100); + + $scope.$on('onRefreshing', function(e) { + icon.style[ionic.CSS.TRANSFORM] = 'scale(2)'; + }); + + $scope.$on('onRefresh', function(e) { + icon.style[ionic.CSS.TRANSFORM] = 'scale(1)'; + }); + $scope.$on('onRefreshOpening', onRefreshOpening); + } } }) +.directive('scroll-refresher', function() { + return { + restrict: 'E', + replace: true, + transclude: true, + template: '
    ' + } +}); + + })(); diff --git a/js/ext/angular/src/directive/ionicList.js b/js/ext/angular/src/directive/ionicList.js index c5fd09d2ec..64e84b8080 100644 --- a/js/ext/angular/src/directive/ionicList.js +++ b/js/ext/angular/src/directive/ionicList.js @@ -3,10 +3,10 @@ angular.module('ionic.ui.list', ['ngAnimate']) -.directive('listItem', ['$timeout', function($timeout) { +.directive('item', ['$timeout', function($timeout) { return { restrict: 'E', - require: ['?^list', '?^virtualList'], + require: ['?^list'], replace: true, transclude: true, scope: { @@ -17,8 +17,9 @@ angular.module('ionic.ui.list', ['ngAnimate']) canReorder: '@', canSwipe: '@', buttons: '=', + type: '@' }, - template: '\ + template: '\
    \ \
    \ @@ -32,20 +33,6 @@ angular.module('ionic.ui.list', ['ngAnimate']) \
    ', - /* - template: '
  • \ -
    \ - \ -
    \ -
    \ -
    \ -
    \ - \ -
    \ -
    \ - \ -
    \ -
  • ',*/ link: function($scope, $element, $attr, list) { // Grab the parent list controller if(list[0]) { @@ -54,6 +41,13 @@ angular.module('ionic.ui.list', ['ngAnimate']) list = list[1]; } + // Add the list item type class + $element.addClass($attr.type || 'item-slider'); + + if($attr.type !== 'item-slider') { + $scope.canSwipe = false; + } + $scope.isEditing = false; $scope.deleteIcon = list.scope.deleteIcon; $scope.reorderIcon = list.scope.reorderIcon; @@ -90,6 +84,7 @@ angular.module('ionic.ui.list', ['ngAnimate']) isEditing: '=', deleteIcon: '@', reorderIcon: '@', + hasPullToRefresh: '@', onRefresh: '&', onRefreshOpening: '&' }, @@ -112,12 +107,14 @@ angular.module('ionic.ui.list', ['ngAnimate']) var lv = new ionic.views.ListView({ el: $element[0], listEl: $element[0].children[0], - hasPullToRefresh: (typeof $scope.onRefresh !== 'undefined'), + hasPullToRefresh: ($scope.hasPullToRefresh !== 'false'), onRefresh: function() { $scope.onRefresh(); + $scope.$parent.$broadcast('onRefresh'); }, onRefreshOpening: function(amt) { $scope.onRefreshOpening({amount: amt}); + $scope.$parent.$broadcast('onRefreshOpening', amt); } }); diff --git a/js/ext/angular/src/directive/ionicNav.js b/js/ext/angular/src/directive/ionicNav.js index 128ff4cbba..380ada41be 100644 --- a/js/ext/angular/src/directive/ionicNav.js +++ b/js/ext/angular/src/directive/ionicNav.js @@ -8,6 +8,31 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges angular.extend(this, ionic.controllers.NavController.prototype); + /** + * Push a template onto the navigation stack. + * @param {string} templateUrl the URL of the template to load. + */ + this.pushFromTemplate = ionic.debounce(function(templateUrl) { + var childScope = $scope.$new(); + childScope.isVisible = true; + + // Load the given template + TemplateLoader.load(templateUrl).then(function(templateString) { + + // 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')); + $animate.enter(cloned, angular.element(content)); + }); + }); + }, 300, true); + + // Pop function, debounced + this.popController = ionic.debounce(function() { + _this.pop(); + }, 300, true); + + ionic.controllers.NavController.call(this, { content: { }, @@ -31,7 +56,7 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges // Support Android hardware back button (native only, not mobile web) var onHardwareBackButton = function(e) { $scope.$apply(function() { - _this.pop(); + _this.popController(); }); } Platform.onHardwareBackButton(onHardwareBackButton); @@ -44,28 +69,6 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges this.endDrag = function(e) { }; - /** - * Push a template onto the navigation stack. - * @param {string} templateUrl the URL of the template to load. - */ - this.pushFromTemplate = function(templateUrl) { - var childScope = $scope.$new(); - childScope.isVisible = true; - - // Load the given template - TemplateLoader.load(templateUrl).then(function(templateString) { - - // 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')); - - //content.append(cloned); - //angular.element(content).append(cloned); - $animate.enter(cloned, angular.element(content)); - }); - }); - }; - /** * Push a controller to the stack. This is called by the child * nav-content directive when it is linked to a scope on the page. @@ -112,7 +115,7 @@ angular.module('ionic.ui.nav', ['ionic.service.templateLoad', 'ionic.service.ges scope.$watch('navController.controllers.length', function(value) { }); scope.goBack = function() { - navCtrl.pop(); + navCtrl.popController(); }; } }; @@ -169,7 +172,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 +188,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 +217,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/ext/angular/src/directive/ionicSideMenu.js b/js/ext/angular/src/directive/ionicSideMenu.js index d596846bcf..07841e7062 100644 --- a/js/ext/angular/src/directive/ionicSideMenu.js +++ b/js/ext/angular/src/directive/ionicSideMenu.js @@ -20,27 +20,18 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture']) angular.extend(this, ionic.controllers.SideMenuController.prototype); ionic.controllers.SideMenuController.call(this, { + // Our quick implementation of the left side menu left: { width: 270, - pushDown: function() { - $scope.leftZIndex = -1; - }, - bringUp: function() { - $scope.leftZIndex = 0; - } }, + + // Our quick implementation of the right side menu right: { width: 270, - pushDown: function() { - $scope.rightZIndex = -1; - }, - bringUp: function() { - $scope.rightZIndex = 0; - } } }); - $scope.contentTranslateX = 0; + $scope.sideMenuContentTranslateX = 0; $scope.sideMenuCtrl = this; }) @@ -72,28 +63,32 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture']) defaultPrevented = e.defaultPrevented; }); - Gesture.on('drag', function(e) { + var dragFn = function(e) { if(defaultPrevented) { return; } sideMenuCtrl._handleDrag(e); - }, $element[0]); + }; - Gesture.on('release', function(e) { + Gesture.on('drag', dragFn, $element[0]); + + var dragReleaseFn = function(e) { if(!defaultPrevented) { sideMenuCtrl._endDrag(e); } defaultPrevented = false; - }, $element[0]); + }; + + Gesture.on('release', dragReleaseFn, $element[0]); sideMenuCtrl.setContent({ onDrag: function(e) {}, endDrag: function(e) {}, getTranslateX: function() { - return $scope.contentTranslateX || 0; + return $scope.sideMenuContentTranslateX || 0; }, setTranslateX: function(amount) { - $scope.contentTranslateX = amount; + $scope.sideMenuContentTranslateX = amount; $element[0].style.webkitTransform = 'translate3d(' + amount + 'px, 0, 0)'; }, enableAnimation: function() { @@ -107,6 +102,12 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture']) $element[0].classList.remove('menu-animated'); } }); + + // Cleanup + $scope.$on('$destroy', function() { + Gesture.off('drag', dragFn); + Gesture.off('release', dragReleaseFn); + }); }; } }; @@ -125,10 +126,23 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture']) return function($scope, $element, $attr, sideMenuCtrl) { $scope.side = $attr.side; + if($scope.side == 'left') { sideMenuCtrl.left.isEnabled = true; + sideMenuCtrl.left.pushDown = function() { + $element[0].style.zIndex = -1; + }; + sideMenuCtrl.left.bringUp = function() { + $element[0].style.zIndex = 0; + }; } else if($scope.side == 'right') { sideMenuCtrl.right.isEnabled = true; + sideMenuCtrl.right.pushDown = function() { + $element[0].style.zIndex = -1; + }; + sideMenuCtrl.right.bringUp = function() { + $element[0].style.zIndex = 0; + }; } $element.append(transclude($scope)); diff --git a/js/ext/angular/test/list.html b/js/ext/angular/test/list.html index acf6b55c85..915f46cf13 100644 --- a/js/ext/angular/test/list.html +++ b/js/ext/angular/test/list.html @@ -64,19 +64,31 @@
    - - - - + + +
    + +
    +
    + + {{item.text}} -
    +
    @@ -86,32 +98,6 @@ - - + + +