From dbe4e3901d6ee70bae85e48b6e58c097b9f2810e Mon Sep 17 00:00:00 2001 From: Andy Joslin Date: Wed, 19 Mar 2014 11:36:35 -0600 Subject: [PATCH] feat(ionic): remove all delegates BREAKING CHANGE: $ionicScrollDelegate, $ionicSlideBoxDelegate, and $ionicSideMenuDelegate have been removed. - $ionicScrollDelegate has been changed to $ionicScrollController. Documentation: [ionContent]( http://ajoslin.github.io/docs/nightly/api/directive/ionContent), [ionScroll]( http://ajoslin.github.io/docs/nightly/api/directive/ionScroll) Change your code from this: ```html ``` ```js function MyCtrl($scope, $ionicScrollDelegate) { $scope.scrollBottom = function() { $ionicScrollDelegate.scrollBottom(); }; } ``` To this: ```html ``` ```js function MyCtrl($scope) { $scope.scrollBottom = function() { $scope.$ionicScrollController.scrollBottom(); }; } ``` - $ionicSideMenuDelegate has been changed to $ionicSideMenusController. Documentation: [ionSideMenus](http://ajoslin.github.io/docs/nightly/api/directive/ionSideMenus) Change your code from this: ```html Side Menu Left ``` ```js function MyCtrl($scope, $ionicSideMenuDelegate) { $scope.toggleLeftMenu = function() { $ionicSideMenuDelegate.toggleLeft(); }; } ``` To this: ```html Side Menu Left ``` ```js function MyCtrl($scope) { $scope.toggleLeftMenu = function() { $scope.$ionicSideMenuController.toggleLeft(); }; } ``` - $ionicSlideBoxDelegate has been removed and upgraded to $ionicSlideBoxController. It had only one method that was unneeded. [Documentation]( http://ajoslin.github.io/docs/nightly/api/directive/ionSlideBox) --- .../src/controller/ionicScrollController.js | 152 +++++++--- js/ext/angular/src/directive/ionicBar.js | 29 +- js/ext/angular/src/directive/ionicContent.js | 21 +- js/ext/angular/src/directive/ionicScroll.js | 5 + js/ext/angular/src/directive/ionicSideMenu.js | 2 +- js/ext/angular/src/directive/ionicTabBar.js | 3 + js/ext/angular/src/ionicAngular.js | 6 - .../service/delegates/ionicScrollDelegate.js | 266 ----------------- js/ext/angular/src/service/ionicView.js | 8 +- js/ext/angular/test/anchorScroll.html | 5 +- js/ext/angular/test/content.html | 11 +- .../controller/ionicScrollController.unit.js | 205 ++++++++++++-- .../angular/test/directive/ionicBar.unit.js | 52 ++++ .../test/directive/ionicContent.unit.js | 37 +-- .../test/directive/ionicRefresher.unit.js | 6 +- .../test/directive/ionicScroll.unit.js | 30 +- .../test/directive/ionicTabBar.unit.js | 1 - js/ext/angular/test/scroll.html | 4 +- .../delegates/ionicScrollDelegate.unit.js | 267 ------------------ .../test/service/ionicViewService.unit.js | 3 - js/ext/angular/test/slideBoxDynamic.html | 4 +- js/utils/dom.js | 10 +- js/views/scrollView.js | 2 - 23 files changed, 436 insertions(+), 693 deletions(-) delete mode 100644 js/ext/angular/src/service/delegates/ionicScrollDelegate.js delete mode 100644 js/ext/angular/test/service/delegates/ionicScrollDelegate.unit.js 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('
') + el.data('$$ionicScrollController', { + scrollTop: jasmine.createSpy('scrollTop') + }); + ionic.requestAnimationFrame = function(cb) { cb(); }; + $compile(el)($rootScope.$new()); + $rootScope.$apply(); + }); + return el; + } + it('should listen for tap, unlisten on destroy', function() { + var callback; + spyOn(ionic, 'on').andCallFake(function(name, cb) { + callback = cb; + }); + spyOn(ionic, 'off'); + var el = setup(); + expect(ionic.on).toHaveBeenCalledWith('tap', jasmine.any(Function), el[0]); + expect(ionic.off).not.toHaveBeenCalled(); + el.scope().$destroy(); + expect(ionic.off).toHaveBeenCalledWith('tap', callback, el[0]); + }); + it('should ignore tap if it\'s in a button', function() { + var el = setup(); + spyOn(ionic.DomUtil, 'rectContains'); + var child = angular.element('
'); + el.append(child); + ionic.trigger('tap', { target: child[0] }, true, true); + expect(ionic.DomUtil.rectContains).not.toHaveBeenCalled(); + }); + it('should scrollTop if tap is inside headerBar', function() { + var el = setup(); + spyOn(ionic.DomUtil, 'rectContains').andCallFake(function() { + return true; + }); + ionic.trigger('tap', { target: el[0], touches: [{pageX:0,pageY:0}] }); + expect(el.controller('$ionicScroll').scrollTop).toHaveBeenCalledWith(true); + }); + it('should not scrollTop if tap isnt inside headerBar', function() { + var el = setup(); + spyOn(ionic.DomUtil, 'rectContains').andCallFake(function() { + return false; + }); + ionic.trigger('tap', { target: el[0], touches: [{pageX:0,pageY:0}] }); + expect(el.controller('$ionicScroll').scrollTop).not.toHaveBeenCalled(); + }); + }); + angular.forEach([{ tag: 'ion-header-bar', element: 'header', diff --git a/js/ext/angular/test/directive/ionicContent.unit.js b/js/ext/angular/test/directive/ionicContent.unit.js index a43a314b66..329c0b45f4 100644 --- a/js/ext/angular/test/directive/ionicContent.unit.js +++ b/js/ext/angular/test/directive/ionicContent.unit.js @@ -31,44 +31,21 @@ describe('Ionic Content directive', function() { expect(scrollElement.hasClass('padding')).toEqual(false); }); - it('Disables bouncing when has-bouncing = false', function() { - ionic.Platform.setPlatform('iPhone'); - var element = compile('')(scope); - scope.$apply(); - var newScope = element.isolateScope(); - var scrollView = scope.scrollView; - expect(scrollView.options.bouncing).toBe(false); - }); - - it('Disables bouncing by default on Android', function() { - ionic.Platform.setPlatform('Android'); - var element = compile('')(scope); - scope.$apply(); - var newScope = element.isolateScope(); - var scrollView = scope.scrollView; - expect(scrollView.options.bouncing).toBe(false); - }); - - it('Disables bouncing by default on Android unless', function() { - ionic.Platform.setPlatform('Android'); - var element = compile('')(scope); - scope.$apply(); - var newScope = element.isolateScope(); - var scrollView = scope.scrollView; - expect(scrollView.options.bouncing).toBe(true); - }); - - it('Should set start x and y', function() { var element = compile('')(scope); scope.$apply(); - var newScope = element.isolateScope(); - var scrollView = scope.scrollView; + var scrollView = scope.$ionicScrollController.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/ionicRefresher.unit.js b/js/ext/angular/test/directive/ionicRefresher.unit.js index 97facf2045..fdf1369b16 100644 --- a/js/ext/angular/test/directive/ionicRefresher.unit.js +++ b/js/ext/angular/test/directive/ionicRefresher.unit.js @@ -5,7 +5,7 @@ describe('ionRefresher directive', function() { inject(function($compile, $rootScope) { var scope = $rootScope.$new(); var ionicScrollCtrl = { - setRefresher: jasmine.createSpy('setRefresher'), + _setRefresher: jasmine.createSpy('setRefresher'), scrollView: { finishPullToRefresh: jasmine.createSpy('finishPullToRefresh') } @@ -50,8 +50,8 @@ describe('ionRefresher directive', function() { it('should setRefresher on scrollCtrl', function() { var el = setup(); - expect(el.controller('$ionicScroll').setRefresher.callCount).toBe(1); - expect(el.controller('$ionicScroll').setRefresher).toHaveBeenCalledWith( + expect(el.controller('$ionicScroll')._setRefresher.callCount).toBe(1); + expect(el.controller('$ionicScroll')._setRefresher).toHaveBeenCalledWith( el.scope(), el[0] ) }); diff --git a/js/ext/angular/test/directive/ionicScroll.unit.js b/js/ext/angular/test/directive/ionicScroll.unit.js index 18b13459ae..f2ae0519b4 100644 --- a/js/ext/angular/test/directive/ionicScroll.unit.js +++ b/js/ext/angular/test/directive/ionicScroll.unit.js @@ -31,38 +31,16 @@ describe('Ionic Scroll Directive', function() { expect(scrollElement.hasClass('padding')).toEqual(true); }); - // it('Enables bouncing by default', function() { - // ionic.Platform.setPlatform('iPhone'); - // element = compile('')(scope); - // scope.$apply(); - // var newScope = element.isolateScope(); - // var scrollView = scope.scrollView; - // expect(scrollView.options.bouncing).toBe(true); - // }); - - it('Disables bouncing when has-bouncing = false', function() { - ionic.Platform.setPlatform('iPhone'); - element = compile('')(scope); + it('should pass attr.controllerBind ionicScrollController', function() { + var element = compile('')(scope); scope.$apply(); - var newScope = element.isolateScope(); - var scrollView = scope.scrollView; - expect(scrollView.options.bouncing).toBe(false); - }); - - it('Disables bouncing by default on Android', function() { - ionic.Platform.setPlatform('Android'); - element = compile('')(scope); - scope.$apply(); - var newScope = element.isolateScope(); - var scrollView = scope.scrollView; - expect(scrollView.options.bouncing).toBe(false); + expect(scope.scrolly).toBe(element.controller('$ionicScroll')); }); it('Should set start x and y', function() { element = compile('')(scope); scope.$apply(); - var newScope = element.isolateScope(); - var scrollView = scope.scrollView; + var scrollView = scope.$ionicScrollController.scrollView; var vals = scrollView.getValues(); expect(vals.left).toBe(100); expect(vals.top).toBe(300); diff --git a/js/ext/angular/test/directive/ionicTabBar.unit.js b/js/ext/angular/test/directive/ionicTabBar.unit.js index 7404b67fc9..e5a9d28ff2 100644 --- a/js/ext/angular/test/directive/ionicTabBar.unit.js +++ b/js/ext/angular/test/directive/ionicTabBar.unit.js @@ -272,7 +272,6 @@ describe('tabs', function() { expect($rootScope.elephant).toBe('banana'); })); - it('should add itself to tabsCtrl and remove on $destroy', function() { var el = setup(); var tab = tabsCtrl.tabs[0]; diff --git a/js/ext/angular/test/scroll.html b/js/ext/angular/test/scroll.html index 6842edd8f3..c2f9fe69f9 100644 --- a/js/ext/angular/test/scroll.html +++ b/js/ext/angular/test/scroll.html @@ -87,7 +87,7 @@ } }) - .controller('ThisCtrl', function($scope, $ionicScrollDelegate) { + .controller('ThisCtrl', function($scope) { var header = document.getElementById('header'); var content = document.getElementById('container'); @@ -103,7 +103,7 @@ } $scope.scrollTo = function() { console.log('scrollTo'); - $ionicScrollDelegate.scrollTo(0, 100, true); + $scope.$ionicScrollController.scrollTo(0, 100, true); }; $scope.onScroll = function(event, scrollTop, scrollLeft) { /* diff --git a/js/ext/angular/test/service/delegates/ionicScrollDelegate.unit.js b/js/ext/angular/test/service/delegates/ionicScrollDelegate.unit.js deleted file mode 100644 index 21b6cc01a1..0000000000 --- a/js/ext/angular/test/service/delegates/ionicScrollDelegate.unit.js +++ /dev/null @@ -1,267 +0,0 @@ - -describe('Ionic ScrollDelegate Service', function() { - var $ionicScrollDelegate, rootScope, compile, timeout, document; - - beforeEach(module('ionic')); - - beforeEach(inject(function(_$ionicScrollDelegate_, $rootScope, $timeout, $compile, $document) { - $ionicScrollDelegate = _$ionicScrollDelegate_; - document = $document; - rootScope = $rootScope; - timeout = $timeout; - compile = $compile; - })); - - it('should just return rootScope if no scrollCtrl', inject(function($rootScope) { - expect(function() { - $ionicScrollDelegate($rootScope); - }).not.toThrow(); - expect($ionicScrollDelegate($rootScope).getScrollView()).toBeFalsy(); - })); - - it('Should get scroll view', function() { - var scope = rootScope.$new(); - var el = compile('')(scope); - var sv = $ionicScrollDelegate(scope).getScrollView(); - expect(sv).not.toBe(undefined); - }); - - it('should resize', function() { - var scope = rootScope.$new(); - var el = compile('')(scope); - var del = $ionicScrollDelegate(scope); - - var sv = del.getScrollView(); - spyOn(sv, 'resize'); - spyOn(sv, 'scrollTo'); - - del.resize(); - timeout.flush(); - expect(sv.resize).toHaveBeenCalled(); - }); - - it('scroll event', function() { - var scope = rootScope.$new(); - var el = compile('')(scope); - scope = el.scope(); - scope.$apply(); - var top, left; - scope.$onScroll = jasmine.createSpy('scroll').andCallFake(function(data) { - top = data.scrollTop; - left = data.scrollLeft; - }); - ionic.trigger('scroll', {target: el[0]}); - expect(scope.$onScroll).toHaveBeenCalled(); - expect(top).toBe(0); - expect(left).toBe(0); - - ionic.trigger('scroll', {target: el[0], scrollTop: 3, scrollLeft: 4}); - expect(top).toBe(3); - expect(left).toBe(4); - }); - - testWithAnimate(true); - testWithAnimate(false); - function testWithAnimate(animate) { - describe('with animate='+animate, function() { - - it('should tapScrollToTop', function() { - var scope = rootScope.$new(); - var el = angular.element('
' + - '
' + - '
' + - '
'); - //ionic.trigger() REALLY doesnt want to work with tap, - //so we just mock on to catch the callback and use that... - var callback; - var del = $ionicScrollDelegate(scope); - spyOn(ionic, 'on').andCallFake(function(eventName, cb) { - callback = cb; - }); - del.tapScrollToTop(el, animate); - - spyOn(del, 'scrollTop'); - //Don't test the rectContains part, too much to mock - spyOn(ionic.DomUtil, 'rectContains').andCallFake(function() { - return true; - }); - callback({ - target: el[0].querySelector('.not-button'), - gesture:{ touches:[{}] } - }); - expect(del.scrollTop).toHaveBeenCalledWith(animate); - - del.scrollTop.reset(); - callback({ - target: el[0].querySelector('.button') - }); - expect(del.scrollTop).not.toHaveBeenCalled(); - }); - it('should resize & scroll top', function() { - var scope = rootScope.$new(); - var el = compile('')(scope); - - var del = $ionicScrollDelegate(scope); - var sv = del.getScrollView(); - spyOn(sv, 'resize'); - spyOn(sv, 'scrollTo'); - del.scrollTop(animate); - - timeout.flush(); - expect(sv.resize).toHaveBeenCalled(); - expect(sv.scrollTo.mostRecentCall.args).toEqual([0, 0, animate]); - }); - - it('should resize & scroll bottom', function() { - var scope = rootScope.$new(); - var el = compile('

')(scope); - - var del = $ionicScrollDelegate(scope); - var sv = del.getScrollView(); - spyOn(sv, 'getScrollMax').andCallFake(function() { - return { left: 10, top: 11 }; - }); - spyOn(sv, 'resize'); - spyOn(sv, 'scrollTo'); - var max = sv.getScrollMax(); - del.scrollBottom(animate); - - timeout.flush(); - expect(sv.resize).toHaveBeenCalled(); - expect(sv.scrollTo.mostRecentCall.args).toEqual([max.left, max.top, animate]); - }); - - it('should resize & scrollTo', function() { - var scope = rootScope.$new(); - var el = compile('

')(scope); - var del = $ionicScrollDelegate(scope); - - var sv = del.getScrollView(); - spyOn(sv, 'scrollTo'); - spyOn(sv, 'resize'); - del.scrollTo(2, 3, animate); - - timeout.flush(); - expect(sv.resize).toHaveBeenCalled(); - expect(sv.scrollTo.mostRecentCall.args).toEqual([2, 3, animate]); - }); - - it('should throw error on rememberScroll if no id', function() { - var scope = rootScope.$new(); - var el = compile(''); - var del = $ionicScrollDelegate(scope); - expect(del.rememberScrollPosition).toThrow(); - }); - - it('scrollToRememberedPosition should scroll if exists', function() { - var scope = rootScope.$new(); - var el = compile('')(scope); - var del = $ionicScrollDelegate(scope); - var sv = del.getScrollView(); - scope.$apply(); - $ionicScrollDelegate._rememberedScrollValues['1'] = { - left: 3, - top: 4 - }; - del.scrollToRememberedPosition('1', animate); - spyOn(sv, 'scrollTo'); - timeout.flush(); - expect(sv.scrollTo).toHaveBeenCalledWith(3, 4, animate); - }); - - it('should save on destroy for rememberScrollPosition', function() { - var scope = rootScope.$new(); - var el = compile('')(scope); - var del = $ionicScrollDelegate(scope); - var sv = del.getScrollView(); - scope.$apply(); - $ionicScrollDelegate._rememberedScrollValues['1'] = { - left: -1, - top: -1 - }; - del.rememberScrollPosition('1', animate); - spyOn(sv, 'getValues').andCallFake(function() { - return { foo: 'bar' }; - }); - scope.$destroy(); - expect(sv.getValues).toHaveBeenCalled(); - expect($ionicScrollDelegate._rememberedScrollValues['1']).toEqual({ - foo: 'bar' - }); - }); - }); - } -}); - -describe('anchorScroll', function() { - function setLocationHash(hash) { - inject(function($location) { - $location.hash = function() { return hash; }; - }); - } - - beforeEach(module('ionic')); - - testWithAnimate(true); - testWithAnimate(false); - - function testWithAnimate(animate) { - describe('with animate=' + animate, function() { - var contentEl, scope, $ionicScrollDelegate, timeout; - beforeEach(inject(function($rootScope, $compile, $timeout, $document, _$ionicScrollDelegate_) { - scope = $rootScope.$new(); - contentEl = $compile('')(scope); - - document.body.appendChild(contentEl[0]); - $ionicScrollDelegate = _$ionicScrollDelegate_; - timeout = $timeout; - $rootScope.$apply(); - })); - - it('should anchorScroll to an element with id', function() { - var anchorMe = angular.element('
'); - var del = $ionicScrollDelegate(scope); - var sv = del.getScrollView(); - spyOn(sv, 'scrollTo'); - spyOn(ionic.DomUtil, 'getPositionInParent').andCallFake(function() { - return { left: 2, top: 1 }; - }); - - setLocationHash('anchorMe'); - contentEl.append(anchorMe); - - del.anchorScroll(animate); - timeout.flush(); - expect(sv.scrollTo).toHaveBeenCalledWith(2, 1, animate); - }); - - it('should anchorScroll to top if !$location.hash()', function() { - var del = $ionicScrollDelegate(scope); - var sv = del.getScrollView(); - spyOn(sv, 'scrollTo'); - spyOn(ionic.DomUtil, 'getPositionInParent'); - del.anchorScroll(animate); - timeout.flush(); - - expect(sv.scrollTo).toHaveBeenCalledWith(0, 0, animate); - expect(ionic.DomUtil.getPositionInParent).not.toHaveBeenCalled(); - }); - - it('should anchorScroll to top if element with hash id doesnt exist', function() { - var del = $ionicScrollDelegate(scope); - var sv = del.getScrollView(); - spyOn(sv, 'scrollTo'); - spyOn(ionic.DomUtil, 'getPositionInParent'); - - setLocationHash('doesnotexist'); - del.anchorScroll(animate); - timeout.flush(); - - expect(sv.scrollTo).toHaveBeenCalledWith(0, 0, animate); - expect(ionic.DomUtil.getPositionInParent).not.toHaveBeenCalled(); - }); - }); - } - -}); - diff --git a/js/ext/angular/test/service/ionicViewService.unit.js b/js/ext/angular/test/service/ionicViewService.unit.js index 0395085a1f..260b3a7c54 100644 --- a/js/ext/angular/test/service/ionicViewService.unit.js +++ b/js/ext/angular/test/service/ionicViewService.unit.js @@ -141,7 +141,6 @@ describe('Ionic View Service', function() { registerData = viewService.register({}); currentView = viewService.getCurrentView(); //Set test value for remembered scroll - currentView.rememberedScrollValues = 'foo'; backView = viewService.getBackView(); forwardView = viewService.getForwardView(); expect(backView.stateName).toEqual('about'); @@ -159,8 +158,6 @@ describe('Ionic View Service', function() { currentView = viewService.getCurrentView(); backView = viewService.getBackView(); forwardView = viewService.getForwardView(); - expect(forwardView.rememberedScrollValues).toEqual({}); - expect(currentView.stateName).toEqual('about'); expect(currentView.backViewId).toEqual(backView.viewId); expect(currentView.forwardViewId).toEqual(forwardView.viewId); expect(backView.stateName).toEqual('home'); diff --git a/js/ext/angular/test/slideBoxDynamic.html b/js/ext/angular/test/slideBoxDynamic.html index 45a8059c39..43cd663e78 100644 --- a/js/ext/angular/test/slideBoxDynamic.html +++ b/js/ext/angular/test/slideBoxDynamic.html @@ -57,7 +57,7 @@