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 */ .factory('$$scrollValueCache', function() { return {}; }) .controller('$ionicScroll', [ '$scope', 'scrollViewOptions', '$timeout', '$window', '$$scrollValueCache', '$location', '$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); //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); var deregisterInstance = $ionicScrollDelegate._registerInstance( this, scrollViewOptions.delegateHandle ); if (!angular.isDefined(scrollViewOptions.bouncing)) { ionic.Platform.ready(function() { scrollView.options.bouncing = !ionic.Platform.isAndroid(); }); } var resize = angular.bind(scrollView, scrollView.resize); ionic.on('resize', resize, $window); // set by rootScope listener if needed var backListenDone = angular.noop; $scope.$on('$destroy', function() { deregisterInstance(); ionic.off('resize', resize, $window); $window.removeEventListener('resize', resize); backListenDone(); if (self._rememberScrollId) { $$scrollValueCache[self._rememberScrollId] = scrollView.getValues(); } }); $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 && historyData.viewId; if (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.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); } }); }; 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) { this.resize().then(function() { scrollView.scrollTo(+values.left, +values.top, shouldAnimate); }); } }; /** * @private */ this._setRefresher = function(refresherScope, refresherElement) { var refresher = this.refresher = refresherElement; var refresherHeight = self.refresher.clientHeight || 0; scrollView.activatePullToRefresh(refresherHeight, function() { refresher.classList.add('active'); refresherScope.$onPulling(); }, function() { refresher.classList.remove('refreshing'); refresher.classList.remove('active'); }, function() { refresher.classList.add('refreshing'); refresherScope.$onRefresh(); }); }; }]);