diff --git a/config/build.config.js b/config/build.config.js
index 13d21681c9..f08dae4c41 100644
--- a/config/build.config.js
+++ b/config/build.config.js
@@ -16,13 +16,15 @@ module.exports = {
' *\n' +
' * Licensed under the MIT license. Please see LICENSE for more information.\n'+
' *\n' +
- ' */\n\n',
+ ' */\n\n' +
+ '(function() {\n',
bundleBanner:
'/*!\n' +
' * ionic.bundle.js is a concatenation of:\n' +
' * ionic.js, angular.js, angular-animate.js,\n'+
' * angular-ui-router.js, and ionic-angular.js\n'+
' */\n\n',
+ footer: '\n})();',
ionicFiles: [
// Base
diff --git a/js/ext/angular/src/controller/ionicScrollController.js b/js/ext/angular/src/controller/ionicScrollController.js
index c395df3749..a54bbb3806 100644
--- a/js/ext/angular/src/controller/ionicScrollController.js
+++ b/js/ext/angular/src/controller/ionicScrollController.js
@@ -1,8 +1,173 @@
-(function() {
-'use strict';
-
angular.module('ionic.ui.scroll')
+/**
+ * @ngdoc service
+ * @name $ionicScrollDelegate
+ * @module ionic
+ * @description
+ * Delegate for controlling scrollViews (created by
+ * {@link ionic.directive:ionContent} and
+ * {@link ionic.directive:ionScroll} directives).
+ *
+ * Each method on $ionicScrollDelegate can be called on the service itself to control all scrollViews. Alternatively, one can control one specific scrollView using `withHandle` and `delegate-handle`. See the example below.
+ *
+ * @usage
+ *
+ * Basic Usage:
+ *
+ * ```html
+ *
+ *
+ *
+ *
+ *
+ * ```
+ * ```js
+ * function MainCtrl($scope, $ionicScrollDelegate) {
+ * $scope.scrollTop = function() {
+ * $ionicScrollDelegate.scrollTop();
+ * };
+ * }
+ * ```
+ *
+ * Example of advanced usage, with two scroll areas using `delegate-handle`
+ * for fine control.
+ *
+ * ```html
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * ```
+ * ```js
+ * function MainCtrl($scope, $ionicScrollDelegate) {
+ * $scope.scrollMainToTop = function() {
+ * $ionicScrollDelegate.withHandle('mainScroll').scrollTop();
+ * };
+ * $scope.scrollSmallToTop = function() {
+ * $ionicScrollDelegate.withHandle('small').scrollTop();
+ * };
+ * }
+ * ```
+ */
+
+/**
+ * @ngdoc method
+ * @name $ionicScrollDelegate#withHandle
+ * @param {string} handle
+ * @returns `delegateInstance` A delegate instance that controls only the
+ * scrollView with delegate-handle matching the given handle.
+ */
+.service('$ionicScrollDelegate', delegateService([
+ /**
+ * @ngdoc method
+ * @name $ionicScrollDelegate#resize
+ * @description Tell the scrollView to recalculate the size of its container.
+ */
+ 'resize',
+ /**
+ * @ngdoc method
+ * @name $ionicScrollDelegate#scrollTop
+ * @param {boolean=} shouldAnimate Whether the scroll should animate.
+ */
+ 'scrollTop',
+ /**
+ * @ngdoc method
+ * @name $ionicScrollDelegate#scrollBottom
+ * @param {boolean=} shouldAnimate Whether the scroll should animate.
+ */
+ 'scrollBottom',
+ /**
+ * @ngdoc method
+ * @name $ionicScrollDelegate#scroll
+ * @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',
+ /**
+ * @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',
+ /**
+ * @ngdoc method
+ * @name $ionicScrollDelegate#rememberScrollPosition
+ * @description
+ * Will make it so, when this scrollView is destroyed (user leaves the page),
+ * the last scroll position the page was on will be saved, indexed by the
+ * given id.
+ *
+ * Note: for pages associated with a view under an ion-nav-view,
+ * rememberScrollPosition automatically saves their scroll.
+ *
+ * Related methods: scrollToRememberedPosition, forgetScrollPosition (below).
+ *
+ * In the following example, the scroll position of the ion-scroll element
+ * will persist, even when the user changes the toggle switch.
+ *
+ * ```html
+ *
+ *
+ *
+ *
+ * {{i}}
+ *
+ *
+ *
+ * ```
+ * ```js
+ * function ScrollCtrl($scope, $ionicScrollDelegate) {
+ * var delegate = $ionicScrollDelegate.withHandle('myScroll');
+ *
+ * // Put any unique ID here. The point of this is: every time the controller is recreated
+ * // we want to load the correct remembered scroll values.
+ * delegate.rememberScrollPosition('my-scroll-id');
+ * delegate.scrollToRememberedPosition();
+ * $scope.items = [];
+ * for (var i=0; i<100; i++) {
+ * $scope.items.push(i);
+ * }
+ * }
+ * ```
+ *
+ * @param {string} id The id to remember the scroll position of this
+ * scrollView by.
+ */
+ 'rememberScrollPosition',
+ /**
+ * @ngdoc method
+ * @name $ionicScrollDelegate#forgetScrollPosition
+ * @description
+ * Stop remembering the scroll position for this scrollView.
+ */
+ 'forgetScrollPosition',
+ /**
+ * @ngdoc method
+ * @name $ionicScrollDelegate#scrollToRememberedPosition
+ * @description
+ * If this scrollView has an id associated with its scroll position,
+ * (through calling rememberScrollPosition), and that position is remembered,
+ * load the position and scroll to it.
+ * @param {boolean=} shouldAnimate Whether to animate the scroll.
+ */
+ 'scrollToRememberedPosition'
+]))
+
/**
* @private
*/
@@ -10,14 +175,6 @@ angular.module('ionic.ui.scroll')
return {};
})
-/**
- * @ngdoc controller
- * @name ionicScroll
- * @module ionic
- * @description
- * Controller for the {@link ionic.directive:ionContent} and
- * {@link ionic.directive:ionScroll} directives.
- */
.controller('$ionicScroll', [
'$scope',
'scrollViewOptions',
@@ -25,13 +182,18 @@ angular.module('ionic.ui.scroll')
'$window',
'$$scrollValueCache',
'$location',
- '$parse',
'$rootScope',
'$document',
-function($scope, scrollViewOptions, $timeout, $window, $$scrollValueCache, $location, $parse, $rootScope, $document) {
+ '$ionicScrollDelegate',
+ '$parse', //DEPRECATED
+function($scope, scrollViewOptions, $timeout, $window, $$scrollValueCache, $location, $rootScope, $document, $ionicScrollDelegate, $parse) {
var self = this;
+ this._scrollViewOptions = scrollViewOptions; //for testing
+
+ $parse('$ionicScrollController').assign($scope.$parent || $scope, this);
+
var element = this.element = scrollViewOptions.el;
var $element = this.$element = angular.element(element);
var scrollView = this.scrollView = new ionic.views.Scroll(scrollViewOptions);
@@ -42,8 +204,9 @@ function($scope, scrollViewOptions, $timeout, $window, $$scrollValueCache, $loca
($element.parent().length ? $element.parent() : $element)
.data('$$ionicScrollController', this);
- $parse(scrollViewOptions.controllerBind || '$ionicScrollController')
- .assign($scope.$parent || $scope, this);
+ var deregisterInstance = $ionicScrollDelegate._registerInstance(
+ this, scrollViewOptions.delegateHandle
+ );
if (!angular.isDefined(scrollViewOptions.bouncing)) {
ionic.Platform.ready(function() {
@@ -58,6 +221,7 @@ function($scope, scrollViewOptions, $timeout, $window, $$scrollValueCache, $loca
var backListenDone = angular.noop;
$scope.$on('$destroy', function() {
+ deregisterInstance();
ionic.off('resize', resize, $window);
$window.removeEventListener('resize', resize);
backListenDone();
@@ -101,31 +265,16 @@ function($scope, scrollViewOptions, $timeout, $window, $$scrollValueCache, $loca
this._rememberScrollId = null;
- /**
- * @ngdoc method
- * @name ionicScroll#resize
- * @description Tell the scrollView to recalculate the size of its container.
- */
this.resize = function() {
return $timeout(resize);
};
- /**
- * @ngdoc method
- * @name ionicScroll#scrollTop
- * @param {boolean=} shouldAnimate Whether the scroll should animate.
- */
this.scrollTop = function(shouldAnimate) {
this.resize().then(function() {
scrollView.scrollTo(0, 0, !!shouldAnimate);
});
};
- /**
- * @ngdoc method
- * @name ionicScroll#scrollBottom
- * @param {boolean=} shouldAnimate Whether the scroll should animate.
- */
this.scrollBottom = function(shouldAnimate) {
this.resize().then(function() {
var max = scrollView.getScrollMax();
@@ -133,29 +282,12 @@ function($scope, scrollViewOptions, $timeout, $window, $$scrollValueCache, $loca
});
};
- /**
- * @ngdoc method
- * @name ionicScroll#scroll
- * @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.
- */
this.scrollTo = function(left, top, shouldAnimate) {
this.resize().then(function() {
scrollView.scrollTo(left, top, !!shouldAnimate);
});
};
- /**
- * @ngdoc method
- * @name ionicScroll#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.
- */
this.anchorScroll = function(shouldAnimate) {
this.resize().then(function() {
var hash = $location.hash();
@@ -169,75 +301,16 @@ function($scope, scrollViewOptions, $timeout, $window, $$scrollValueCache, $loca
});
};
- /**
- * @ngdoc method
- * @name ionicScroll#rememberScrollPosition
- * @description
- * Will make it so, when this scrollView is destroyed (user leaves the page),
- * the last scroll position the page was on will be saved, indexed by the
- * given id.
- *
- * Note: for pages associated with a view under an ion-nav-view,
- * rememberScrollPosition automatically saves their scroll.
- *
- * Related methods: scrollToRememberedPosition, forgetScrollPosition (below).
- *
- * In the following example, the scroll position of the ion-scroll element
- * will persist, even when the user changes the toggle switch.
- *
- * ```html
- *
- *
- *
- *
- * {{i}}
- *
- *
- *
- * ```
- * ```js
- * function ScrollCtrl($scope) {
- * // Put any unique ID here. The point of this is: every time the controller is recreated
- * // we want to load the correct remembered scroll values.
- * $scope.$ionicScrollController.rememberScrollPosition('my-scroll-id');
- *
- * $scope.$ionicScrollController.scrollToRememberedPosition();
-
- * $scope.items = [];
- * for (var i=0; i<100; i++) {
- * $scope.items.push(i);
- * }
- * }
- * ```
- *
- * @param {string} id The id to remember the scroll position of this
- * scrollView by.
- */
this.rememberScrollPosition = function(id) {
if (!id) {
throw new Error("Must supply an id to remember the scroll by!");
}
this._rememberScrollId = id;
};
- /**
- * @ngdoc method
- * @name ionicScroll#forgetScrollPosition
- * @description
- * Stop remembering the scroll position for this scrollView.
- */
this.forgetScrollPosition = function() {
delete $$scrollValueCache[this._rememberScrollId];
this._rememberScrollId = null;
};
- /**
- * @ngdoc method
- * @name ionicScroll#scrollToRememberedPosition
- * @description
- * If this scrollView has an id associated with its scroll position,
- * (through calling rememberScrollPosition), and that position is remembered,
- * load the position and scroll to it.
- * @param {boolean=} shouldAnimate Whether to animate the scroll.
- */
this.scrollToRememberedPosition = function(shouldAnimate) {
var values = $$scrollValueCache[this._rememberScrollId];
if (values) {
@@ -268,4 +341,3 @@ function($scope, scrollViewOptions, $timeout, $window, $$scrollValueCache, $loca
};
}]);
-})();
diff --git a/js/ext/angular/src/directive/ionicContent.js b/js/ext/angular/src/directive/ionicContent.js
index f25443212a..129daa0a73 100644
--- a/js/ext/angular/src/directive/ionicContent.js
+++ b/js/ext/angular/src/directive/ionicContent.js
@@ -28,7 +28,6 @@ angular.module('ionic.ui.content', ['ionic.ui.scroll'])
* @ngdoc directive
* @name ionContent
* @module ionic
- * @controller ionicScroll as $scope.$ionicScrollController
* @restrict E
*
* @description
@@ -44,9 +43,8 @@ angular.module('ionic.ui.content', ['ionic.ui.scroll'])
* directive, and infinite scrolling with the {@link ionic.directive:ionInfiniteScroll}
* directive.
*
- * @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=} delegate-handle The handle used to identify this scrollView
+ * with {@link ionic.service:$ionicScrollDelegate}.
* @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.
@@ -122,7 +120,7 @@ function($parse, $timeout, $controller, $ionicBind) {
$scope: $scope,
scrollViewOptions: {
el: $element[0],
- controllerBind: $attr.controllerBind,
+ delegateHandle: attr.delegateHandle,
bouncing: $scope.$eval($scope.hasBouncing),
startX: $scope.$eval($scope.startX) || 0,
startY: $scope.$eval($scope.startY) || 0,
diff --git a/js/ext/angular/src/directive/ionicScroll.js b/js/ext/angular/src/directive/ionicScroll.js
index a946019a2f..5d5b49baea 100644
--- a/js/ext/angular/src/directive/ionicScroll.js
+++ b/js/ext/angular/src/directive/ionicScroll.js
@@ -7,15 +7,13 @@ 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=} delegate-handle The handle used to identify this scrollView
+ * with {@link ionic.service:$ionicScrollDelegate}.
* @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}.
@@ -63,7 +61,7 @@ angular.module('ionic.ui.scroll', [])
var scrollViewOptions= {
el: $element[0],
- controllerBind: $attr.controllerBind,
+ delegateHandle: $attr.delegateHandle,
paging: isPaging,
scrollbarX: $scope.$eval($scope.scrollbarX) !== false,
scrollbarY: $scope.$eval($scope.scrollbarY) !== false,
diff --git a/js/ext/angular/src/service/delegateFactory.js b/js/ext/angular/src/service/delegateFactory.js
new file mode 100644
index 0000000000..30f8b03707
--- /dev/null
+++ b/js/ext/angular/src/service/delegateFactory.js
@@ -0,0 +1,66 @@
+
+function delegateService(methodNames) {
+ return ['$log', function($log) {
+ var delegate = this;
+
+ this._instances = {};
+ this._registerInstance = function(instance, handle) {
+ handle || (handle = ionic.Utils.nextUid());
+ delegate._instances[handle] = instance;
+
+ return function deregister() {
+ delete delegate._instances[handle];
+ };
+ };
+
+ this.withHandle = function(handle) {
+ if (!handle) {
+ return delegate;
+ }
+ return new InstanceWithHandle(handle);
+ };
+
+ /*
+ * Creates a new object that will have all the methodNames given,
+ * and call them on the given the controller instance matching given
+ * handle.
+ * The reason we don't just let withHandle return the controller instance
+ * itself is that the controller instance might not exist yet.
+ *
+ * We want people to be able to do
+ * `var instance = $ionicScrollDelegate.withHandle('foo')` on controller
+ * instantiation, but on controller instantiation a child directive
+ * may not have been compiled yet!
+ *
+ * So this is our way of solving this problem: we create an object
+ * that will only try to fetch the controller with given handle
+ * once the methods are actually called.
+ */
+ function InstanceWithHandle(handle) {
+ this.handle = handle;
+ }
+ methodNames.forEach(function(methodName) {
+ InstanceWithHandle.prototype[methodName] = function() {
+ var instance = delegate._instances[this.handle];
+ if (!instance) {
+ return $log.error(
+ 'Delegate with handle "'+this.handle+'" could not find a',
+ 'corresponding element with delegate-handle="'+this.handle+'"!',
+ methodName, 'was not called!');
+ }
+ return instance[methodName].apply(instance, arguments);
+ };
+ delegate[methodName] = function() {
+ var args = arguments;
+ var returnValue;
+ angular.forEach(delegate._instances, function(instance) {
+ var result = instance[methodName].apply(instance, args);
+ if (!angular.isDefined(returnValue)) {
+ returnValue = result;
+ }
+ });
+ return returnValue;
+ };
+ });
+ }];
+}
diff --git a/js/ext/angular/test/anchorScroll.html b/js/ext/angular/test/anchorScroll.html
index ef8e910175..ce1823525c 100644
--- a/js/ext/angular/test/anchorScroll.html
+++ b/js/ext/angular/test/anchorScroll.html
@@ -35,7 +35,7 @@
diff --git a/js/ext/angular/test/controller/ionicScrollController.unit.js b/js/ext/angular/test/controller/ionicScrollController.unit.js
index 91b9fff0f0..d9c9556a6f 100644
--- a/js/ext/angular/test/controller/ionicScrollController.unit.js
+++ b/js/ext/angular/test/controller/ionicScrollController.unit.js
@@ -27,17 +27,28 @@ describe('$ionicScroll Controller', function() {
});
}
- it('should set $scope.$ionicScrollController by default', function() {
- setup()
- expect(scope.$ionicScrollController).toBe(ctrl);
- });
+ it('should register with no handle', inject(function($ionicScrollDelegate) {
+ spyOn($ionicScrollDelegate, '_registerInstance');
+ var el = setup();
+ expect($ionicScrollDelegate._registerInstance)
+ .toHaveBeenCalledWith(ctrl, undefined);
+ }));
- it('should set options.controllerBind to ctrl', function() {
- setup({
- controllerBind: 'something'
+ it('should register with given handle and deregister on destroy', inject(function($ionicScrollDelegate) {
+ var deregisterSpy = jasmine.createSpy('deregister');
+ spyOn($ionicScrollDelegate, '_registerInstance').andCallFake(function() {
+ return deregisterSpy;
});
- expect(scope.something).toBe(ctrl);
- });
+ setup({
+ delegateHandle: 'something'
+ });
+ expect($ionicScrollDelegate._registerInstance)
+ .toHaveBeenCalledWith(ctrl, 'something');
+
+ expect(deregisterSpy).not.toHaveBeenCalled();
+ scope.$destroy();
+ expect(deregisterSpy).toHaveBeenCalled();
+ }));
it('should set bouncing to option if given', function() {
spyOn(ionic.Platform, 'isAndroid');
diff --git a/js/ext/angular/test/directive/ionicContent.unit.js b/js/ext/angular/test/directive/ionicContent.unit.js
index cde7db7bd2..7b7cc5b0cf 100644
--- a/js/ext/angular/test/directive/ionicContent.unit.js
+++ b/js/ext/angular/test/directive/ionicContent.unit.js
@@ -16,6 +16,12 @@ describe('Ionic Content directive', function() {
expect(element.controller('$ionicScroll').element).toBe(element[0]);
});
+ it('passes delegateHandle attribute', function() {
+ var element = compile('')(scope);
+ expect(element.controller('$ionicScroll')._scrollViewOptions.delegateHandle)
+ .toBe('handleMe');
+ });
+
it('Has content class', function() {
var element = compile('')(scope);
expect(element.hasClass('scroll-content')).toBe(true);
@@ -46,18 +52,12 @@ describe('Ionic Content directive', function() {
it('Should set start x and y', function() {
var element = compile('')(scope);
scope.$apply();
- var scrollView = scope.$ionicScrollController.scrollView;
+ var scrollView = element.controller('$ionicScroll').scrollView;
var vals = scrollView.getValues();
expect(vals.left).toBe(100);
expect(vals.top).toBe(300);
});
- it('should pass attr.controllerBind ionicScrollController', function() {
- var element = compile('')(scope);
- scope.$apply();
- expect(scope.scrolly).toBe(element.controller('$ionicScroll'));
- });
-
});
/* Tests #555 */
describe('Ionic Content Directive scoping', function() {
diff --git a/js/ext/angular/test/directive/ionicScroll.unit.js b/js/ext/angular/test/directive/ionicScroll.unit.js
index d235432b6c..f424635ee0 100644
--- a/js/ext/angular/test/directive/ionicScroll.unit.js
+++ b/js/ext/angular/test/directive/ionicScroll.unit.js
@@ -14,9 +14,10 @@ describe('Ionic Scroll Directive', function() {
});
}));
- it('Has $ionicScroll controller', function() {
- element = compile('')(scope);
- expect(element.controller('$ionicScroll').element).toBe(element[0]);
+ it('passes delegateHandle attribute', function() {
+ var element = compile('')(scope);
+ expect(element.controller('$ionicScroll')._scrollViewOptions.delegateHandle)
+ .toBe('handleThis');
});
it('has $onScroll (used by $ionicScrollController)', function() {
@@ -44,16 +45,10 @@ describe('Ionic Scroll Directive', function() {
expect(scrollElement.hasClass('padding')).toEqual(true);
});
- it('should pass attr.controllerBind ionicScrollController', function() {
- var element = compile('')(scope);
- scope.$apply();
- expect(scope.scrolly).toBe(element.controller('$ionicScroll'));
- });
-
it('Should set start x and y', function() {
element = compile('')(scope);
scope.$apply();
- var scrollView = scope.$ionicScrollController.scrollView;
+ var scrollView = element.controller('$ionicScroll').scrollView;
var vals = scrollView.getValues();
expect(vals.left).toBe(100);
expect(vals.top).toBe(300);
diff --git a/js/ext/angular/test/rememberScroll.html b/js/ext/angular/test/rememberScroll.html
index f6ac6b2ea6..f6f4af14df 100644
--- a/js/ext/angular/test/rememberScroll.html
+++ b/js/ext/angular/test/rememberScroll.html
@@ -10,7 +10,7 @@
-
+