diff --git a/js/ext/angular/src/controller/ionicScrollController.js b/js/ext/angular/src/controller/ionicScrollController.js
index fd0b066069..6f81d322e9 100644
--- a/js/ext/angular/src/controller/ionicScrollController.js
+++ b/js/ext/angular/src/controller/ionicScrollController.js
@@ -6,15 +6,36 @@ angular.module('ionic.ui.scroll')
/**
* @private
*/
-.controller('$ionicScroll', ['$scope', 'scrollViewOptions', '$timeout', '$ionicScrollDelegate', '$window', function($scope, scrollViewOptions, $timeout, $ionicScrollDelegate, $window) {
+.factory('$$scrollValueCache', function() {
+ return {};
+})
+
+.controller('$ionicScroll', [
+ '$scope',
+ 'scrollViewOptions',
+ '$timeout',
+ '$window',
+ '$$scrollValueCache',
+ '$location',
+ '$parse',
+ '$rootScope',
+ '$document',
+function($scope, scrollViewOptions, $timeout, $window, $$scrollValueCache, $location, $parse, $rootScope, $document) {
var self = this;
var element = this.element = scrollViewOptions.el;
+ var $element = this.$element = angular.element(element);
var scrollView = this.scrollView = new ionic.views.Scroll(scrollViewOptions);
- this.$scope = $scope;
- $scope.$parent.$$ionicScrollController = this;
+ //Attach self to element as a controller so other directives can require this controller
+ //through `require: '$ionicScroll'
+ //Also attach to parent so that sibling elements can require this
+ ($element.parent().length ? $element.parent() : $element)
+ .data('$$ionicScrollController', this);
+
+ $parse(scrollViewOptions.controllerBind || '$ionicScrollController')
+ .assign($scope.$parent, this);
if (!angular.isDefined(scrollViewOptions.bouncing)) {
ionic.Platform.ready(function() {
@@ -22,42 +43,107 @@ angular.module('ionic.ui.scroll')
});
}
- var $element = this.$element = angular.element(element);
-
- //Attach self to element as a controller so other directives can require this controller
- //through `require: '$ionicScroll'
- $element.data('$$ionicScrollController', this);
-
- //Register delegate for event handling
- $ionicScrollDelegate.register($scope, $element, scrollView);
-
var resize = angular.bind(scrollView, scrollView.resize);
$window.addEventListener('resize', resize);
- $scope.$on('$viewContentLoaded', function(e, historyData) {
- if (e.defaultPrevented) {
- return;
- }
- //only the top-most scroll area under a view should remember that view's
- //scroll position
- e.preventDefault();
-
- var values = historyData && historyData.rememberedScrollValues;
- if (values) {
- $timeout(function() {
- scrollView.scrollTo(+values.left || null, +values.top || null);
- }, 0, false);
- }
- $scope.$on('$destroy', function() {
- historyData && (historyData.rememberedScrollValues = scrollView.getValues());
- });
- });
+ // set by rootScope listener if needed
+ var backListenDone = angular.noop;
$scope.$on('$destroy', function() {
$window.removeEventListener('resize', resize);
+ backListenDone();
+ if (self._rememberScrollId) {
+ $$scrollValueCache[self._rememberScrollId] = scrollView.getValues();
+ }
});
- this.setRefresher = function(refresherScope, refresherElement) {
+ $element.on('scroll', function(e) {
+ var detail = (e.originalEvent || e).detail || {};
+ $scope.$onScroll && $scope.$onScroll({
+ event: e,
+ scrollTop: detail.scrollTop || 0,
+ scrollLeft: detail.scrollLeft || 0
+ });
+ });
+
+ $scope.$on('$viewContentLoaded', function(e, historyData) {
+ //only the top-most scroll area under a view should remember that view's
+ //scroll position
+ if (e.defaultPrevented) { return; }
+ e.preventDefault();
+
+ var viewId = historyData.viewId;
+
+ self.rememberScrollPosition(viewId);
+ self.scrollToRememberedPosition();
+
+ backListenDone = $rootScope.$on('$viewHistory.viewBack', function(e, fromViewId, toViewId) {
+ //When going back from this view, forget its saved scroll position
+ if (viewId === fromViewId) {
+ self.forgetScrollPosition();
+ }
+ });
+ });
+
+ $timeout(function() {
+ scrollView.run();
+ });
+
+ this._rememberScrollId = null;
+ this.rememberScrollPosition = function(id) {
+ if (!id) {
+ throw new Error("Must supply an id to remember the scroll by!");
+ }
+ this._rememberScrollId = id;
+ };
+ this.forgetScrollPosition = function() {
+ delete $$scrollValueCache[this._rememberScrollId];
+ this._rememberScrollId = null;
+ };
+ this.scrollToRememberedPosition = function(shouldAnimate) {
+ var values = $$scrollValueCache[this._rememberScrollId];
+ if (values) {
+ scrollView.scrollTo(+values.left, +values.top, shouldAnimate);
+ }
+ };
+
+ this.resize = function() {
+ return $timeout(resize);
+ };
+ this.scrollTop = function(shouldAnimate) {
+ this.resize().then(function() {
+ scrollView.scrollTo(0, 0, !!shouldAnimate);
+ });
+ };
+ this.scrollBottom = function(shouldAnimate) {
+ this.resize().then(function() {
+ var max = scrollView.getScrollMax();
+ scrollView.scrollTo(max.left, max.top, !!shouldAnimate);
+ });
+ };
+ this.scrollTo = function(left, top, shouldAnimate) {
+ this.resize().then(function() {
+ scrollView.scrollTo(left, top, !!shouldAnimate);
+ });
+ };
+ this.anchorScroll = function(shouldAnimate) {
+ this.resize().then(function() {
+ var hash = $location.hash();
+ var elm = hash && $document[0].getElementById(hash);
+ if (hash && elm) {
+ var scroll = ionic.DomUtil.getPositionInParent(elm, self.$element);
+ scrollView.scrollTo(scroll.left, scroll.top, !!shouldAnimate);
+ } else {
+ scrollView.scrollTo(0,0, !!shouldAnimate);
+ }
+ });
+ };
+
+
+ /**
+ * @private
+ */
+ this._setRefresher = function(refresherScope, refresherElement) {
var refresher = this.refresher = refresherElement;
var refresherHeight = self.refresher.clientHeight || 0;
scrollView.activatePullToRefresh(refresherHeight, function() {
@@ -71,10 +157,6 @@ angular.module('ionic.ui.scroll')
refresherScope.$onRefresh();
});
};
-
- $timeout(function() {
- scrollView.run();
- });
}]);
})();
diff --git a/js/ext/angular/src/directive/ionicBar.js b/js/ext/angular/src/directive/ionicBar.js
index 5844c01cd6..5696fb4b1e 100644
--- a/js/ext/angular/src/directive/ionicBar.js
+++ b/js/ext/angular/src/directive/ionicBar.js
@@ -3,11 +3,36 @@
angular.module('ionic.ui.header', ['ngAnimate', 'ngSanitize'])
-.directive('barHeader', ['$ionicScrollDelegate', function($ionicScrollDelegate) {
+.directive('barHeader', ['$document', function($document) {
return {
restrict: 'C',
link: function($scope, $element, $attr) {
- $ionicScrollDelegate($scope).tapScrollToTop($element);
+ ionic.requestAnimationFrame(function() {
+ var scrollCtrl = $element.controller('$ionicScroll');
+ if (!scrollCtrl) {
+ return;
+ }
+
+ ionic.on('tap', onTap, $element[0]);
+ $scope.$on('$destroy', function() {
+ ionic.off('tap', onTap, $element[0]);
+ });
+
+ function onTap(e) {
+ if (ionic.DomUtil.getParentOrSelfWithClass(e.target, 'button', 4)) {
+ return;
+ }
+ var touch = e.gesture && e.gesture.touches[0] || e.detail.touches[0];
+ var bounds = $element[0].getBoundingClientRect();
+ if(ionic.DomUtil.rectContains(
+ touch.pageX, touch.pageY,
+ bounds.left, bounds.top - 20,
+ bounds.left + bounds.width, bounds.top + bounds.height)
+ ) {
+ scrollCtrl.scrollTop(true);
+ }
+ }
+ });
}
};
}])
diff --git a/js/ext/angular/src/directive/ionicContent.js b/js/ext/angular/src/directive/ionicContent.js
index 2bbd72afe1..795ed73f9d 100644
--- a/js/ext/angular/src/directive/ionicContent.js
+++ b/js/ext/angular/src/directive/ionicContent.js
@@ -1,7 +1,7 @@
(function() {
'use strict';
-angular.module('ionic.ui.content', ['ionic.ui.service', 'ionic.ui.scroll'])
+angular.module('ionic.ui.content', ['ionic.ui.scroll'])
/**
* Panel is a simple 100% width and height, fixed panel. It's meant for content to be
@@ -28,6 +28,8 @@ angular.module('ionic.ui.content', ['ionic.ui.service', 'ionic.ui.scroll'])
* @ngdoc directive
* @name ionContent
* @module ionic
+ * @controller ionicScroll as $scope.$ionicScrollController
+ * @restrict E
*
* @description
* The ionContent directive provides an easy to use content area that can be configured
@@ -45,6 +47,9 @@ angular.module('ionic.ui.content', ['ionic.ui.service', 'ionic.ui.scroll'])
* Use the classes 'has-header', 'has-subheader', 'has-footer', and 'has-tabs'
* to modify the positioning of the ion-content relative to surrounding elements.
*
+ * @param {string=} controller-bind The scope variable to bind this element's scrollView's
+ * {@link ionic.controller:ionicScroll ionicScroll controller} to.
+ * Default: $scope.$ionicScrollController.
* @param {boolean=} padding Whether to add padding to the content.
* of the content. Defaults to true on iOS, false on Android.
* @param {boolean=} scroll Whether to allow scrolling of content. Defaults to true.
@@ -67,6 +72,7 @@ function($parse, $timeout, $controller, $ionicBind) {
transclude: true,
require: '^?ionNavView',
scope: true,
+ priority: 501,
template:
'
' +
'
' +
@@ -97,9 +103,11 @@ function($parse, $timeout, $controller, $ionicBind) {
scrollEventInterval: '@'
});
- $scope.$watch($attr.padding, function(newVal) {
- scrollContent.toggleClass('padding', !!newVal);
- });
+ if (angular.isDefined($attr.padding)) {
+ $scope.$watch($attr.padding, function(newVal) {
+ scrollContent.toggleClass('padding', !!newVal);
+ });
+ }
if ($scope.scroll === "false") {
//do nothing
@@ -111,6 +119,7 @@ function($parse, $timeout, $controller, $ionicBind) {
$scope: $scope,
scrollViewOptions: {
el: $element[0],
+ controllerBind: $attr.controllerBind,
bouncing: $scope.$eval($scope.hasBouncing),
startX: $scope.$eval($scope.startX) || 0,
startY: $scope.$eval($scope.startY) || 0,
@@ -128,7 +137,7 @@ function($parse, $timeout, $controller, $ionicBind) {
}
});
//Publish scrollView to parent so children can access it
- scrollView = $scope.$parent.scrollView = scrollCtrl.scrollView;
+ scrollView = scrollCtrl.scrollView;
}
transclude($scope, function(clone) {
@@ -227,7 +236,7 @@ function($parse, $timeout, $controller, $ionicBind) {
$onPulling: '&onPulling'
});
- scrollCtrl.setRefresher($scope, $element[0]);
+ scrollCtrl._setRefresher($scope, $element[0]);
$scope.$on('scroll.refreshComplete', function() {
$element[0].classList.remove('active');
scrollCtrl.scrollView.finishPullToRefresh();
diff --git a/js/ext/angular/src/directive/ionicScroll.js b/js/ext/angular/src/directive/ionicScroll.js
index 806394f812..45e627afd1 100644
--- a/js/ext/angular/src/directive/ionicScroll.js
+++ b/js/ext/angular/src/directive/ionicScroll.js
@@ -7,11 +7,15 @@ angular.module('ionic.ui.scroll', [])
* @ngdoc directive
* @name ionScroll
* @module ionic
+ * @controller ionicScroll as $scope.$ionicScrollController
* @restrict E
*
* @description
* Creates a scrollable container for all content inside.
*
+ * @param {string=} controller-bind The scope variable to bind this element's scrollView's
+ * {@link ionic.controller:ionicScroll ionicScroll controller} to.
+ * Default: $scope.$ionicScrollController.
* @param {string=} direction Which way to scroll. 'x' or 'y'. Default 'y'.
* @param {boolean=} paging Whether to scroll with paging.
* @param {expression=} on-refresh Called on pull-to-refresh, triggered by an {@link ionic.directive:ionRefresher}.
@@ -61,6 +65,7 @@ angular.module('ionic.ui.scroll', [])
var scrollViewOptions= {
el: $element[0],
+ controllerBind: $attr.controllerBind,
paging: isPaging,
scrollbarX: $scope.$eval($scope.scrollbarX) !== false,
scrollbarY: $scope.$eval($scope.scrollbarY) !== false,
diff --git a/js/ext/angular/src/directive/ionicSideMenu.js b/js/ext/angular/src/directive/ionicSideMenu.js
index 0fbfd269f1..57be388b94 100644
--- a/js/ext/angular/src/directive/ionicSideMenu.js
+++ b/js/ext/angular/src/directive/ionicSideMenu.js
@@ -94,7 +94,7 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture', 'ionic.service.vie
* ```js
* function ContentController($scope) {
* $scope.toggleLeft = function() {
- * $scope.sideMenuController.toggleLeft();
+ * $scope.$ionicSideMenusController.toggleLeft();
* };
* }
* ```
diff --git a/js/ext/angular/src/directive/ionicTabBar.js b/js/ext/angular/src/directive/ionicTabBar.js
index 275653e5ee..8aaf1d5209 100644
--- a/js/ext/angular/src/directive/ionicTabBar.js
+++ b/js/ext/angular/src/directive/ionicTabBar.js
@@ -270,6 +270,9 @@ function($rootScope, $animate, $ionicBind, $compile, $ionicViewService) {
tabsCtrl = ctrls[0],
tabCtrl = ctrls[1];
+ //Remove title attribute so browser-tooltip does not apear
+ $element[0].removeAttribute('title');
+
$ionicBind($scope, $attr, {
animate: '=',
onSelect: '&',
diff --git a/js/ext/angular/src/ionicAngular.js b/js/ext/angular/src/ionicAngular.js
index 744a180e05..0740f1c441 100644
--- a/js/ext/angular/src/ionicAngular.js
+++ b/js/ext/angular/src/ionicAngular.js
@@ -23,11 +23,6 @@ angular.module('ionic.service', [
'ionic.decorator.location'
]);
-// UI specific services and delegates
-angular.module('ionic.ui.service', [
- 'ionic.ui.service.scrollDelegate'
-]);
-
angular.module('ionic.ui', [
'ionic.ui.checkbox',
'ionic.ui.content',
@@ -47,7 +42,6 @@ angular.module('ionic.ui', [
angular.module('ionic', [
'ionic.service',
- 'ionic.ui.service',
'ionic.ui',
// Angular deps
diff --git a/js/ext/angular/src/service/delegates/ionicScrollDelegate.js b/js/ext/angular/src/service/delegates/ionicScrollDelegate.js
deleted file mode 100644
index 55d843ae09..0000000000
--- a/js/ext/angular/src/service/delegates/ionicScrollDelegate.js
+++ /dev/null
@@ -1,266 +0,0 @@
-(function() {
-'use strict';
-
-angular.module('ionic.ui.service.scrollDelegate', [])
-
-/**
- * @ngdoc service
- * @name $ionicScrollDelegate
- * @module ionic
- * @description
- * Allows you to have some control over a scrollable area (created by an
- * {@link ionic.directive:ionContent} or {@link ionic.directive:ionScroll}
- * directive).
- *
- * Inject it into a controller, create a new instance based upon the current scope,
- * and its methods will send messages to the nearest scrollView and its children.
- *
- * @usage
- * ```js
- * function MyController($scope, $ionicScrollDelegate) {
- * var delegate = $ionicScrollDelegate($scope);
- * $scope.scrollToTop = function() {
- * delegate.scrollTop();
- * };
- * }
- * ```
- * ```html
- *
- *
- *
- * ```
- */
-.factory('$ionicScrollDelegate', ['$rootScope', '$timeout', '$location', '$ionicViewService', function($rootScope, $timeout, $location, $ionicViewService) {
- //Exposed for testing
- var rememberedScrollValues = ionicScrollDelegate._rememberedScrollValues = {};
-
- function getScrollCtrl($scope) {
- var ctrl;
- while ($scope) {
- if ( (ctrl = $scope.$$ionicScrollController) ) {
- return ctrl;
- }
- $scope = $scope.$parent;
- }
- return ctrl;
- }
-
- function ionicScrollDelegate($scope) {
- var scrollCtrl = getScrollCtrl($scope);
- var scrollScope = scrollCtrl && scrollCtrl.$scope || $rootScope;
-
- return {
- /**
- * @ngdoc method
- * @name $ionicScrollDelegate#scrollTop
- * @description
- * @param {boolean=} shouldAnimate Whether the scroll should animate.
- */
- scrollTop: function(animate) {
- scrollScope.$broadcast('scroll.scrollTop', animate);
- },
- /**
- * @ngdoc method
- * @name $ionicScrollDelegate#scrollBottom
- * @description
- * @param {boolean=} shouldAnimate Whether the scroll should animate.
- */
- scrollBottom: function(animate) {
- scrollScope.$broadcast('scroll.scrollBottom', animate);
- },
- /**
- * @ngdoc method
- * @name $ionicScrollDelegate#scroll
- * @description
- * @param {number} left The x-value to scroll to.
- * @param {number} top The y-value to scroll to.
- * @param {boolean=} shouldAnimate Whether the scroll should animate.
- */
- scrollTo: function(left, top, animate) {
- scrollScope.$broadcast('scroll.scrollTo', left, top, animate);
- },
- /**
- * @ngdoc method
- * @name $ionicScrollDelegate#anchorScroll
- * @description
- *
- * Tell the scrollView to scroll to the element with an id
- * matching window.location.hash.
- *
- * If no matching element is found, it will scroll to top.
- *
- * @param {boolean=} shouldAnimate Whether the scroll should animate.
- */
- anchorScroll: function(animate) {
- scrollScope.$broadcast('scroll.anchorScroll', animate);
- },
- /**
- * @ngdoc method
- * @name $ionicScrollDelegate#resize
- * @description
- *
- * Tell the scrollView to recalculate the size of its container.
- */
- resize: function() {
- scrollScope.$broadcast('scroll.resize');
- },
- /**
- * @private
- */
- tapScrollToTop: function(element, animate) {
- var _this = this;
- if (!angular.isDefined(animate)) {
- animate = true;
- }
-
- ionic.on('tap', function(e) {
- var target = e.target;
- //Don't scroll to top for a button click
- if (ionic.DomUtil.getParentOrSelfWithClass(target, 'button')) {
- return;
- }
-
- var el = element[0];
- var bounds = el.getBoundingClientRect();
-
- if(ionic.DomUtil.rectContains(e.gesture.touches[0].pageX, e.gesture.touches[0].pageY, bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + 20)) {
- _this.scrollTop(animate);
- }
- }, element[0]);
- },
-
- /**
- * @ngdoc method
- * @name $ionicScrollDelegate#rememberScrollPosition
- * @description
- *
- * When this scroll area is destroyed, its last scroll position will be
- * saved using the given id.
- *
- * @param {string} id The identifier for this saved scroll position.
- */
- rememberScrollPosition: function(id) {
- if (!id) {
- throw new Error("Must supply a unique id!");
- }
- scrollScope.$broadcast('scroll.rememberPosition', id);
- },
-
- /**
- * @ngdoc method
- * @name $ionicScrollDelegate#scrollToRememberedPosition
- * @description
- *
- * If a scroll position was remembered using the given id, loads the
- * remembered scroll position and scrolls there.
- *
- * @param {string} id The identifier for this saved scroll position.
- * @param {boolean=} shouldAnimate Whether to animate the scroll.
- */
- scrollToRememberedPosition: function(id, animate) {
- if (!id) {
- throw new Error("Must supply a unique id!");
- }
- scrollScope.$broadcast('scroll.scrollToRememberedPosition', id, !!animate);
- },
-
- /**
- * @private
- * Attempt to get the current scroll view in scope (if any)
- *
- * Note: will not work in an isolated scope context.
- */
- getScrollView: function() {
- return scrollCtrl && scrollCtrl.scrollView;
- }
- };
- }
-
- /**
- * @private
- * Register a scope and scroll view for scroll event handling.
- * $scope {Scope} the scope to register and listen for events
- */
- ionicScrollDelegate.register = function($scope, $element, scrollView) {
-
- var scrollEl = $element[0];
-
- function scrollViewResize() {
- // Run the resize after this digest
- return $timeout(function() {
- scrollView.resize();
- });
- }
-
- $element.on('scroll', function(e) {
- var detail = (e.originalEvent || e).detail || {};
-
- $scope.$onScroll && $scope.$onScroll({
- event: e,
- scrollTop: detail.scrollTop || 0,
- scrollLeft: detail.scrollLeft || 0
- });
-
- });
-
- $scope.$on('scroll.resize', scrollViewResize);
-
- $scope.$on('scroll.anchorScroll', function(e, animate) {
- scrollViewResize().then(function() {
- var hash = $location.hash();
- var elm;
- if (hash && (elm = document.getElementById(hash)) ) {
- var scroll = ionic.DomUtil.getPositionInParent(elm, scrollEl);
- scrollView.scrollTo(scroll.left, scroll.top, !!animate);
- } else {
- scrollView.scrollTo(0,0, !!animate);
- }
- });
- });
-
- $scope.$on('scroll.scrollTo', function(e, left, top, animate) {
- scrollViewResize().then(function() {
- scrollView.scrollTo(left, top, !!animate);
- });
- });
- $scope.$on('scroll.scrollTop', function(e, animate) {
- scrollViewResize().then(function() {
- scrollView.scrollTo(0, 0, !!animate);
- });
- });
- $scope.$on('scroll.scrollBottom', function(e, animate) {
- scrollViewResize().then(function() {
- var sv = scrollView;
- if (sv) {
- var max = sv.getScrollMax();
- sv.scrollTo(max.left, max.top, !!animate);
- }
- });
- });
-
- var rememberScrollId;
- $scope.$on('scroll.rememberPosition', function(e, id) {
- rememberScrollId = id;
- });
- $scope.$on('$destroy', function() {
- if (rememberScrollId) {
- rememberedScrollValues[rememberScrollId] = scrollView.getValues();
- }
- });
-
- $scope.$on('scroll.scrollToRememberedPosition', function(e, id, animate) {
- var values = rememberedScrollValues[id];
- if (values) {
- scrollViewResize().then(function() {
- scrollView.scrollTo(+values.left || null, +values.top || null, animate);
- });
- }
- });
- };
-
- return ionicScrollDelegate;
-}]);
-
-})(ionic);
diff --git a/js/ext/angular/src/service/ionicView.js b/js/ext/angular/src/service/ionicView.js
index 0ba8085f15..0e79afe1be 100644
--- a/js/ext/angular/src/service/ionicView.js
+++ b/js/ext/angular/src/service/ionicView.js
@@ -159,8 +159,6 @@ angular.module('ionic.service.view', ['ui.router', 'ionic.service.platform'])
rsp.viewId = backView.viewId;
rsp.navAction = 'moveBack';
rsp.viewId = backView.viewId;
- //when going back, erase scrollValues
- currentView.rememberedScrollValues = {};
if(backView.historyId === currentView.historyId) {
// went back in the same history
rsp.navDirection = 'back';
@@ -234,9 +232,13 @@ angular.module('ionic.service.view', ['ui.router', 'ionic.service.platform'])
stateName: this.getCurrentStateName(),
stateParams: this.getCurrentStateParams(),
url: $location.url(),
- rememberedScrollValues: null
});
+ if (rsp.navAction == 'moveBack') {
+ //moveBack(from, to);
+ $rootScope.$emit('$viewHistory.viewBack', currentView.viewId, rsp.viewId);
+ }
+
// add the new view to this history's stack
hist.stack.push(viewHistory.views[rsp.viewId]);
}
diff --git a/js/ext/angular/test/anchorScroll.html b/js/ext/angular/test/anchorScroll.html
index 105f51d377..ef8e910175 100644
--- a/js/ext/angular/test/anchorScroll.html
+++ b/js/ext/angular/test/anchorScroll.html
@@ -35,8 +35,7 @@
diff --git a/js/ext/angular/test/content.html b/js/ext/angular/test/content.html
index d6c0ef9a0b..1e695dcd1a 100644
--- a/js/ext/angular/test/content.html
+++ b/js/ext/angular/test/content.html
@@ -116,18 +116,13 @@
}
})
- .controller('TestCtrl', function($scope, $timeout, $ionicScrollDelegate) {
- var delegate = $ionicScrollDelegate($scope);
+ .controller('TestCtrl', function($scope, $timeout) {
console.log('CONSTRUCT');
-
$timeout(function() {
- var view = delegate.getScrollView($scope);
- console.log(view);
});
})
- .controller('ThisCtrl', function($scope, $timeout, $ionicScrollDelegate) {
- var delegate = $ionicScrollDelegate($scope);
+ .controller('ThisCtrl', function($scope, $timeout) {
var header = document.getElementById('header');
var content = document.getElementById('container');
var startTop = header.offsetTop;
@@ -139,7 +134,7 @@
var last = 0;
$scope.onRefresh = function() {
$timeout(function() {
- delegate.finishRefreshing($scope);
+ $scope.$broadcast('scroll.refreshComplete');
}, 1000);
};
$scope.onScroll = function(event, scrollTop, scrollLeft) {
diff --git a/js/ext/angular/test/controller/ionicScrollController.unit.js b/js/ext/angular/test/controller/ionicScrollController.unit.js
index 3e0682319c..35feb5fa13 100644
--- a/js/ext/angular/test/controller/ionicScrollController.unit.js
+++ b/js/ext/angular/test/controller/ionicScrollController.unit.js
@@ -10,6 +10,7 @@ describe('$ionicScroll Controller', function() {
//scrollView requires an outer container element and a child
//content element
angular.element('')[0];
+ angular.element('').append(options.el);
inject(function($controller, $rootScope, $timeout) {
scope = $rootScope.$new();
@@ -22,15 +23,35 @@ describe('$ionicScroll Controller', function() {
});
}
- it('should set this.$scope', function() {
- setup();
- //Just an arbitrary way of checking that it is indeed a scope
- expect(typeof ctrl.$scope.$id).toBe('string');
+ it('should set $scope.$ionicScrollController by default', function() {
+ setup()
+ expect(scope.$ionicScrollController).toBe(ctrl);
});
- it('should set $scope.$$ionicScrollController', function() {
+ it('should set options.controllerBind to ctrl', function() {
+ setup({
+ controllerBind: 'something'
+ });
+ expect(scope.something).toBe(ctrl);
+ });
+
+ it('should set bounce to !isAndroid after platformReady, if not options.boucing', function() {
+ var isAndroid;
+ spyOn(ionic.Platform, 'isAndroid').andCallFake(function() {
+ return isAndroid;
+ });
+ setup({
+ bouncing: true
+ });
+ expect(ionic.Platform.isAndroid).not.toHaveBeenCalled();
+
+ isAndroid = true;
setup();
- expect(ctrl.$scope.$$ionicScrollController).toBe(ctrl);
+ expect(ctrl.scrollView.options.bouncing).toBe(false);
+
+ isAndroid = false;
+ setup();
+ expect(ctrl.scrollView.options.bouncing).toBe(true);
});
it('should set this.element and this.$element', function() {
@@ -46,7 +67,7 @@ describe('$ionicScroll Controller', function() {
it('should register controller on element.data', function() {
setup();
- expect(ctrl.$element.controller('$ionicScroll')).toBe(ctrl);
+ expect(ctrl.$element.parent().data('$$ionicScrollController')).toBe(ctrl);
});
it('should run after a timeout', function() {
@@ -64,24 +85,57 @@ describe('$ionicScroll Controller', function() {
});
it('should remember scroll position on $viewContentLoaded event', function() {
- var historyData = { rememberedScrollValues: { left: 1, top: 2 } };
+ var historyData = { viewId: '1' };
setup();
- spyOn(ctrl.scrollView, 'scrollTo');
+ spyOn(ctrl, 'rememberScrollPosition');
+ spyOn(ctrl, 'scrollToRememberedPosition');
scope.$broadcast('$viewContentLoaded', historyData);
timeout.flush();
- expect(ctrl.scrollView.scrollTo).toHaveBeenCalledWith(1, 2);
+ expect(ctrl.rememberScrollPosition).toHaveBeenCalledWith('1');
+ expect(ctrl.scrollToRememberedPosition).toHaveBeenCalled();
+ });
+ it('should forget on $viewHistory.viewBack after $viewContentLoaded', inject(function($rootScope) {
+ var historyData = { viewId: 'foo' };
+ setup();
+ spyOn($rootScope, '$on').andCallThrough();;
+ scope.$broadcast('$viewContentLoaded', historyData);
+ expect(scope.$on).toHaveBeenCalledWith('$viewHistory.viewBack', jasmine.any(Function));
+
+ //Should not forget unless backViewId is the same
+ spyOn(ctrl, 'forgetScrollPosition');
+ $rootScope.$broadcast('$viewHistory.viewBack', 'bar');
+ expect(ctrl.forgetScrollPosition).not.toHaveBeenCalled();
+ $rootScope.$broadcast('$viewHistory.viewBack', 'foo');
+ expect(ctrl.forgetScrollPosition).toHaveBeenCalled();
+ }));
+
+ it('should not remember scrollValues on $destroy without id', inject(function($$scrollValueCache) {
+ setup();
+ spyOn(ctrl.scrollView, 'getValues');
+ scope.$destroy();
+ expect(ctrl.scrollView.getValues).not.toHaveBeenCalled();
+ expect($$scrollValueCache).toEqual({});
+ }));
+
+ it('should remember scrollValues on $destroy with id', inject(function($$scrollValueCache) {
+ setup();
+ ctrl.rememberScrollPosition('super');
spyOn(ctrl.scrollView, 'getValues').andCallFake(function() {
- return {
- left: 33,
- top: 44
- };
+ return 'scrollValues';
});
- scope.$broadcast('$destroy');
- expect(historyData.rememberedScrollValues).toEqual({
- left: 33,
- top: 44
+ scope.$destroy();
+ expect(ctrl.scrollView.getValues).toHaveBeenCalled();
+ expect($$scrollValueCache).toEqual({
+ 'super': 'scrollValues'
});
+ }));
+
+ it('rememberScrollPosition should throw without id', function() {
+ setup();
+ expect(function() {
+ ctrl.rememberScrollPosition();
+ }).toThrow();
});
it('should unbind window event listener on scope destroy', function() {
@@ -95,12 +149,119 @@ describe('$ionicScroll Controller', function() {
expect(window.removeEventListener.mostRecentCall.args[0]).toBe('resize');
});
- it('should register with $ionicScrollDelegate', inject(function($ionicScrollDelegate) {
- spyOn($ionicScrollDelegate, 'register');
+ it('rememberScrollPosition should set id', function() {
setup();
- expect($ionicScrollDelegate.register).toHaveBeenCalledWith(scope, ctrl.$element, ctrl.scrollView);
+ expect(ctrl._rememberScrollId).toBeFalsy();
+ ctrl.rememberScrollPosition('banana');
+ expect(ctrl._rememberScrollId).toBe('banana');
+ });
+
+ it('forgetScrollPosition should remove from cache and unset id', inject(function($$scrollValueCache) {
+ setup();
+ ctrl._rememberScrollId = 'elephant';
+ $$scrollValueCache.elephant = 'stampede';
+ ctrl.forgetScrollPosition();
+ expect(ctrl._rememberScrollId).toBeFalsy();
+ expect($$scrollValueCache.elephant).toBeFalsy();
}));
+ it('should listen to scroll event and call $onScroll', function() {
+ setup();
+ scope.$onScroll = jasmine.createSpy();
+
+ expect(scope.$onScroll).not.toHaveBeenCalled();
+
+ ionic.trigger('scroll', {target: ctrl.element});
+ expect(scope.$onScroll).toHaveBeenCalled();
+ expect(scope.$onScroll.mostRecentCall.args[0].scrollLeft).toBe(0);
+ expect(scope.$onScroll.mostRecentCall.args[0].scrollTop).toBe(0);
+
+ scope.$onScroll.reset();
+ ionic.trigger('scroll', {target: ctrl.element, scrollTop: 3, scrollLeft: 4});
+ expect(scope.$onScroll.mostRecentCall.args[0].scrollLeft).toBe(4);
+ expect(scope.$onScroll.mostRecentCall.args[0].scrollTop).toBe(3);
+ });
+
+ it('.resize() should resize after timeout', inject(function($timeout) {
+ setup();
+ $timeout.flush();
+ spyOn(ctrl.scrollView, 'resize');
+ ctrl.resize();
+ expect(ctrl.scrollView.resize).not.toHaveBeenCalled();
+ $timeout.flush();
+ expect(ctrl.scrollView.resize).toHaveBeenCalled();
+ }));
+
+ [false, true].forEach(function(shouldAnimate) {
+ describe('with animate='+shouldAnimate, function() {
+
+ it('scrollToRememberedPosition should work', inject(function($$scrollValueCache) {
+ setup();
+ spyOn(ctrl.scrollView, 'scrollTo');
+ $$scrollValueCache.foo = { left: 3, top: 4 };
+ ctrl._rememberScrollId = 'foo';
+ expect(ctrl.scrollView.scrollTo).not.toHaveBeenCalled();
+ ctrl.scrollToRememberedPosition(shouldAnimate);
+ expect(ctrl.scrollView.scrollTo).toHaveBeenCalledWith(3, 4, shouldAnimate);
+ }));
+
+ describe('scroll action', function() {
+ beforeEach(function() {
+ setup();
+ //Mock resize to insta-call through for easier tests
+ ctrl.resize = function() {
+ return { then: function(cb) { cb(); } };
+ };
+ });
+ it('.scrollTop', function() {
+ spyOn(ctrl.scrollView, 'scrollTo');
+ ctrl.scrollTop(shouldAnimate);
+ expect(ctrl.scrollView.scrollTo).toHaveBeenCalledWith(0, 0, shouldAnimate);
+ });
+ it('.scrollBottom', function() {
+ spyOn(ctrl.scrollView, 'scrollTo');
+ spyOn(ctrl.scrollView, 'getScrollMax').andCallFake(function() {
+ return { left: 33, top: 44 };
+ });
+ ctrl.scrollBottom(shouldAnimate);
+ expect(ctrl.scrollView.scrollTo).toHaveBeenCalledWith(33, 44, shouldAnimate);
+ });
+ it('.scrollTo', function() {
+ spyOn(ctrl.scrollView, 'scrollTo');
+ ctrl.scrollTo(1, 2, shouldAnimate);
+ expect(ctrl.scrollView.scrollTo).toHaveBeenCalledWith(1, 2, shouldAnimate);
+ });
+ it('.anchorScroll without hash should scrollTop', inject(function($location, $document) {
+ $document[0].getElementById = jasmine.createSpy();
+ $location.hash = function() { return null; };
+ spyOn(ctrl.scrollView, 'scrollTo');
+ ctrl.anchorScroll(shouldAnimate);
+ expect($document[0].getElementById).not.toHaveBeenCalled();
+ expect(ctrl.scrollView.scrollTo).toHaveBeenCalledWith(0, 0, shouldAnimate);
+ }));
+ it('.anchorScroll with hash but no element should scrollTop', inject(function($location, $document) {
+ $document[0].getElementById = jasmine.createSpy();
+ $location.hash = function() { return 'foo'; };
+ spyOn(ctrl.scrollView, 'scrollTo');
+ ctrl.anchorScroll(shouldAnimate);
+ expect($document[0].getElementById).toHaveBeenCalledWith('foo');
+ expect(ctrl.scrollView.scrollTo).toHaveBeenCalledWith(0, 0, shouldAnimate);
+ }));
+ it('.anchorScroll with el matching hash should scroll to it', inject(function($location, $document) {
+ $document[0].getElementById = jasmine.createSpy('byId').andCallFake(function() {
+ return { offsetLeft: 8, offsetTop: 9 };
+ });
+ spyOn($location, 'hash').andCallFake(function() {
+ return 'foo';
+ });
+ spyOn(ctrl.scrollView, 'scrollTo');
+ ctrl.anchorScroll(shouldAnimate);
+ expect(ctrl.scrollView.scrollTo).toHaveBeenCalledWith(8, 9, shouldAnimate);
+ }));
+ });
+ });
+ });
+
it('should not activatePullToRefresh if setRefresher is not called', function() {
setup();
timeout.flush();
@@ -119,7 +280,7 @@ describe('$ionicScroll Controller', function() {
refreshingCb = refreshing;
doneCb = done;
});
- ctrl.setRefresher(scope, ctrl.element);
+ ctrl._setRefresher(scope, ctrl.element);
var scrollOnRefreshSpy = jasmine.createSpy('scroll.onRefresh');
diff --git a/js/ext/angular/test/directive/ionicBar.unit.js b/js/ext/angular/test/directive/ionicBar.unit.js
index 936fcb7d75..76211794c2 100644
--- a/js/ext/angular/test/directive/ionicBar.unit.js
+++ b/js/ext/angular/test/directive/ionicBar.unit.js
@@ -1,6 +1,58 @@
describe('bar directives', function() {
beforeEach(module('ionic'));
+ describe('barHeader tapScrollToTop', function() {
+ function setup() {
+ var el;
+ inject(function($compile, $rootScope) {
+ el = angular.element('