diff --git a/js/ext/angular/src/controller/ionicScrollController.js b/js/ext/angular/src/controller/ionicScrollController.js index 0011d15133..23a858603c 100644 --- a/js/ext/angular/src/controller/ionicScrollController.js +++ b/js/ext/angular/src/controller/ionicScrollController.js @@ -13,6 +13,9 @@ angular.module('ionic.ui.scroll') var element = this.element = scrollViewOptions.el; var scrollView = this.scrollView = new ionic.views.Scroll(scrollViewOptions); + this.$scope = $scope; + $scope.$parent.$$ionicScrollController = this; + if (!angular.isDefined(scrollViewOptions.bouncing)) { ionic.Platform.ready(function() { scrollView.options.bouncing = !ionic.Platform.isAndroid(); diff --git a/js/ext/angular/src/directive/ionicBar.js b/js/ext/angular/src/directive/ionicBar.js index 25bfb2ac9f..f954e09f08 100644 --- a/js/ext/angular/src/directive/ionicBar.js +++ b/js/ext/angular/src/directive/ionicBar.js @@ -7,8 +7,7 @@ angular.module('ionic.ui.header', ['ngAnimate', 'ngSanitize']) return { restrict: 'C', link: function($scope, $element, $attr) { - // We want to scroll to top when the top of this element is clicked - $ionicScrollDelegate.tapScrollToTop($element); + $ionicScrollDelegate($scope).tapScrollToTop($element); } }; }]) @@ -26,8 +25,9 @@ angular.module('ionic.ui.header', ['ngAnimate', 'ngSanitize']) * Is able to have left or right buttons, and additionally its title can be * aligned through the {@link ionic.controller:ionicBar ionicBar controller}. * - * @param {string=} model The model to assign this headerBar's - * {@link ionic.controller:ionicBar ionicBar controller} to. + * @param {string=} type The type of the bar. For example 'bar-positive'. + * @param {string=} model The model to assign this headerBar's + * {@link ionic.controller:ionicBar ionicBar controller} to. * Defaults to assigning to $scope.headerBarController. * @param {string=} align-title Where to align the title at the start. * Avaialble: 'left', 'right', or 'center'. Defaults to 'center'. @@ -63,8 +63,9 @@ angular.module('ionic.ui.header', ['ngAnimate', 'ngSanitize']) * Is able to have left or right buttons, and additionally its title can be * aligned through the {@link ionic.controller:ionicBar ionicBar controller}. * - * @param {string=} model The model to assign this footerBar's - * {@link ionic.controller:ionicBar ionicBar controller} to. + * @param {string=} type The type of the bar. For example 'bar-positive'. + * @param {string=} model The model to assign this footerBar's + * {@link ionic.controller:ionicBar ionicBar controller} to. * Defaults to assigning to $scope.footerBarController. * @param {string=} align-title Where to align the title at the start. * Avaialble: 'left', 'right', or 'center'. Defaults to 'center'. @@ -90,9 +91,9 @@ angular.module('ionic.ui.header', ['ngAnimate', 'ngSanitize']) function barDirective(isHeader) { var BAR_TEMPLATE = isHeader ? '
' : - ''; - var BAR_MODEL_DEFAULT = isHeader ? - 'headerBarController' : + ''; + var BAR_MODEL_DEFAULT = isHeader ? + 'headerBarController' : 'footerBarController'; return ['$parse', function($parse) { return { @@ -106,7 +107,12 @@ function barDirective(isHeader) { alignTitle: $attr.alignTitle || 'center' }); - $parse($attr.model || BAR_MODEL_DEFAULT).assign($scope.$parent, hb); + $parse($attr.model || BAR_MODEL_DEFAULT).assign($scope.$parent || $scope, hb); + + $attr.$observe('type', function(val, oldVal) { + oldVal && $element.removeClass(oldVal); + $element.addClass(val); + }); } }; }]; diff --git a/js/ext/angular/src/directive/ionicContent.js b/js/ext/angular/src/directive/ionicContent.js index 6b5e7c06b4..30f12bf22d 100644 --- a/js/ext/angular/src/directive/ionicContent.js +++ b/js/ext/angular/src/directive/ionicContent.js @@ -72,7 +72,7 @@ function($parse, $timeout, $ionicScrollDelegate, $controller, $ionicBind) { require: '^?ionNavView', scope: true, template: - '
' + + '
' + '
' + '
', compile: function(element, attr, transclude) { diff --git a/js/ext/angular/src/service/delegates/ionicScrollDelegate.js b/js/ext/angular/src/service/delegates/ionicScrollDelegate.js index ba98b64cca..1f58595bf3 100644 --- a/js/ext/angular/src/service/delegates/ionicScrollDelegate.js +++ b/js/ext/angular/src/service/delegates/ionicScrollDelegate.js @@ -13,7 +13,9 @@ angular.module('ionic.ui.service.scrollDelegate', []) * {@link ionic.directive:ionContent} or {@link ionic.directive:ionScroll} * directive). * - * Inject it into a controller, and its methods will send messages to the nearest scrollView and all of its children. + * 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 all of + * its children. * * @usage * ```html @@ -26,167 +28,185 @@ angular.module('ionic.ui.service.scrollDelegate', []) * ```js * function MyController($scope, $ionicScrollDelegate) { * $scope.scrollToTop = function() { - * $ionicScrollDelegate.scrollTop(); + * var delegate = $ionicScrollDelegate($scope); + * delegate.scrollTop(); * }; * } * ``` */ -.factory('$ionicScrollDelegate', ['$rootScope', '$timeout', '$q', '$anchorScroll', '$location', '$document', function($rootScope, $timeout, $q, $anchorScroll, $location, $document) { - return { - /** - * @ngdoc method - * @name $ionicScrollDelegate#scrollTop - * @param {boolean=} shouldAnimate Whether the scroll should animate. - */ - scrollTop: function(animate) { - $rootScope.$broadcast('scroll.scrollTop', animate); - }, - /** - * @ngdoc method - * @name $ionicScrollDelegate#scrollBottom - * @param {boolean=} shouldAnimate Whether the scroll should animate. - */ - scrollBottom: function(animate) { - $rootScope.$broadcast('scroll.scrollBottom', animate); - }, - /** - * @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: function(left, top, animate) { - $rootScope.$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) { - $rootScope.$broadcast('scroll.anchorScroll', animate); - }, - /** - * @ngdoc method - * @name $ionicScrollDelegate#resize - * @description Tell the scrollView to recalculate the size of its container. - */ - resize: function() { - $rootScope.$broadcast('scroll.resize'); - }, - /** - * @private - */ - tapScrollToTop: function(element, animate) { - var _this = this; - if (!angular.isDefined(animate)) { - animate = true; - } +.factory('$ionicScrollDelegate', ['$rootScope', '$timeout', '$location', function($rootScope, $timeout, $location) { - 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; + 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 Used on an instance of $ionicScrollDelegate. + * @param {boolean=} shouldAnimate Whether the scroll should animate. + */ + scrollTop: function(animate) { + scrollScope.$broadcast('scroll.scrollTop', animate); + }, + /** + * @ngdoc method + * @name $ionicScrollDelegate#scrollBottom + * @description Used on an instance of $ionicScrollDelegate. + * @param {boolean=} shouldAnimate Whether the scroll should animate. + */ + scrollBottom: function(animate) { + scrollScope.$broadcast('scroll.scrollBottom', animate); + }, + /** + * @ngdoc method + * @name $ionicScrollDelegate#scroll + * @description Used on an instance of $ionicScrollDelegate. + * @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 Used on an instance of $ionicScrollDelegate. + * + * 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 Used on an instance of $ionicScrollDelegate. + * + * 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; } - var el = element[0]; - var bounds = el.getBoundingClientRect(); + 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; + } - 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]); - }, + var el = element[0]; + var bounds = el.getBoundingClientRect(); - finishRefreshing: function($scope) { - $scope.$broadcast('scroll.refreshComplete'); - }, + 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]); + }, - /** - * @private - * Attempt to get the current scroll view in scope (if any) - * - * Note: will not work in an isolated scope context. - */ - getScrollView: function($scope) { - return $scope.scrollView; - }, - - /** - * @private - * Register a scope and scroll view for scroll event handling. - * $scope {Scope} the scope to register and listen for events - */ - register: function($scope, $element, scrollView) { - - var scrollEl = $element[0]; - - function scrollViewResize() { - // Run the resize after this digest - return $timeout(function() { - scrollView.resize(); - }); + /** + * @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; } + }; + } - $element.on('scroll', function(e) { - var detail = (e.originalEvent || e).detail || {}; + /** + * @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) { - $scope.$onScroll && $scope.$onScroll({ - event: e, - scrollTop: detail.scrollTop || 0, - scrollLeft: detail.scrollLeft || 0 - }); + var scrollEl = $element[0]; - }); - - $scope.$parent.$on('scroll.resize', scrollViewResize); - - // Called to stop refreshing on the scroll view - $scope.$parent.$on('scroll.refreshComplete', function(e) { - scrollView.finishPullToRefresh(); - }); - - $scope.$parent.$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.$parent.$on('scroll.scrollTo', function(e, left, top, animate) { - scrollViewResize().then(function() { - scrollView.scrollTo(left, top, !!animate); - }); - }); - $scope.$parent.$on('scroll.scrollTop', function(e, animate) { - scrollViewResize().then(function() { - scrollView.scrollTo(0, 0, !!animate); - }); - }); - $scope.$parent.$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); - } - }); + 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); + } + }); + }); }; + + return ionicScrollDelegate; }]); })(ionic); diff --git a/js/ext/angular/test/anchorScroll.html b/js/ext/angular/test/anchorScroll.html index eb1cd83442..743f5719a1 100644 --- a/js/ext/angular/test/anchorScroll.html +++ b/js/ext/angular/test/anchorScroll.html @@ -23,7 +23,7 @@ - @@ -36,14 +36,15 @@ diff --git a/js/ext/angular/test/content.html b/js/ext/angular/test/content.html index d7d5b10d88..0997e19dfa 100644 --- a/js/ext/angular/test/content.html +++ b/js/ext/angular/test/content.html @@ -121,15 +121,17 @@ }) .controller('TestCtrl', function($scope, $timeout, $ionicScrollDelegate) { + var delegate = $ionicScrollDelegate($scope); console.log('CONSTRUCT'); $timeout(function() { - var view = $ionicScrollDelegate.getScrollView($scope); + var view = delegate.getScrollView($scope); console.log(view); }); }) .controller('ThisCtrl', function($scope, $timeout, $ionicScrollDelegate) { + var delegate = $ionicScrollDelegate($scope); var header = document.getElementById('header'); var content = document.getElementById('container'); var startTop = header.offsetTop; @@ -141,7 +143,7 @@ var last = 0; $scope.onRefresh = function() { $timeout(function() { - $ionicScrollDelegate.finishRefreshing($scope); + delegate.finishRefreshing($scope); }, 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 7c18631ef7..eee1f0e60e 100644 --- a/js/ext/angular/test/controller/ionicScrollController.unit.js +++ b/js/ext/angular/test/controller/ionicScrollController.unit.js @@ -22,6 +22,17 @@ 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', function() { + setup(); + expect(ctrl.$scope.$$ionicScrollController).toBe(ctrl); + }); + it('should set this.element and this.$element', function() { setup(); expect(ctrl.element.tagName).toMatch(/div/i); diff --git a/js/ext/angular/test/list-fit.html b/js/ext/angular/test/list-fit.html index e09a5f1865..593305b952 100644 --- a/js/ext/angular/test/list-fit.html +++ b/js/ext/angular/test/list-fit.html @@ -12,12 +12,19 @@ - + +

Header!

+
- + +
{{$$contentState | json}}
+
{{$$contentState.getClassName()}}
+ + isSub +
  • This ion-list should *exactly* fit
  • between header and footer (no gap),
  • @@ -52,7 +59,7 @@ - +

    Footer!

    diff --git a/js/ext/angular/test/service/delegates/ionicScrollDelegate.unit.js b/js/ext/angular/test/service/delegates/ionicScrollDelegate.unit.js index 0a57aa0104..461af502ac 100644 --- a/js/ext/angular/test/service/delegates/ionicScrollDelegate.unit.js +++ b/js/ext/angular/test/service/delegates/ionicScrollDelegate.unit.js @@ -1,10 +1,10 @@ describe('Ionic ScrollDelegate Service', function() { - var del, rootScope, compile, timeout, document; + var $ionicScrollDelegate, rootScope, compile, timeout, document; beforeEach(module('ionic')); - beforeEach(inject(function($ionicScrollDelegate, $rootScope, $timeout, $compile, $document) { - del = $ionicScrollDelegate; + beforeEach(inject(function(_$ionicScrollDelegate_, $rootScope, $timeout, $compile, $document) { + $ionicScrollDelegate = _$ionicScrollDelegate_; document = $document; rootScope = $rootScope; timeout = $timeout; @@ -14,15 +14,16 @@ describe('Ionic ScrollDelegate Service', function() { it('Should get scroll view', function() { var scope = rootScope.$new(); var el = compile('')(scope); - var sv = del.getScrollView(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(scope); + var sv = del.getScrollView(); spyOn(sv, 'resize'); spyOn(sv, 'scrollTo'); @@ -65,6 +66,7 @@ describe('Ionic ScrollDelegate Service', function() { //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; }); @@ -91,7 +93,8 @@ describe('Ionic ScrollDelegate Service', function() { var scope = rootScope.$new(); var el = compile('')(scope); - var sv = del.getScrollView(scope); + var del = $ionicScrollDelegate(scope); + var sv = del.getScrollView(); spyOn(sv, 'resize'); spyOn(sv, 'scrollTo'); del.scrollTop(animate); @@ -105,7 +108,8 @@ describe('Ionic ScrollDelegate Service', function() { var scope = rootScope.$new(); var el = compile('

    ')(scope); - var sv = del.getScrollView(scope); + var del = $ionicScrollDelegate(scope); + var sv = del.getScrollView(); spyOn(sv, 'getScrollMax').andCallFake(function() { return { left: 10, top: 11 }; }); @@ -122,8 +126,9 @@ describe('Ionic ScrollDelegate Service', function() { it('should resize & scrollTo', function() { var scope = rootScope.$new(); var el = compile('

    ')(scope); + var del = $ionicScrollDelegate(scope); - var sv = del.getScrollView(scope); + var sv = del.getScrollView(); spyOn(sv, 'scrollTo'); spyOn(sv, 'resize'); del.scrollTo(2, 3, animate); @@ -134,17 +139,6 @@ describe('Ionic ScrollDelegate Service', function() { }); }); } - - it('should finish refreshing', function() { - var scope = rootScope.$new(); - var el = compile('')(scope); - - var sv = del.getScrollView(scope); - spyOn(sv, 'finishPullToRefresh'); - - del.finishRefreshing(scope); - expect(sv.finishPullToRefresh).toHaveBeenCalled(); - }); }); describe('anchorScroll', function() { @@ -161,19 +155,21 @@ describe('anchorScroll', function() { function testWithAnimate(animate) { describe('with animate=' + animate, function() { - var contentEl, scope, del, timeout; - beforeEach(inject(function($rootScope, $compile, $timeout, $document, $ionicScrollDelegate) { + var contentEl, scope, $ionicScrollDelegate, timeout; + beforeEach(inject(function($rootScope, $compile, $timeout, $document, _$ionicScrollDelegate_) { scope = $rootScope.$new(); contentEl = $compile('')(scope); document.body.appendChild(contentEl[0]); - del = $ionicScrollDelegate; + $ionicScrollDelegate = _$ionicScrollDelegate_; timeout = $timeout; + $rootScope.$apply(); })); it('should anchorScroll to an element with id', function() { var anchorMe = angular.element('
    '); - var sv = del.getScrollView(scope); + var del = $ionicScrollDelegate(scope); + var sv = del.getScrollView(); spyOn(sv, 'scrollTo'); spyOn(ionic.DomUtil, 'getPositionInParent').andCallFake(function() { return { left: 2, top: 1 }; @@ -188,7 +184,8 @@ describe('anchorScroll', function() { }); it('should anchorScroll to top if !$location.hash()', function() { - var sv = del.getScrollView(scope); + var del = $ionicScrollDelegate(scope); + var sv = del.getScrollView(); spyOn(sv, 'scrollTo'); spyOn(ionic.DomUtil, 'getPositionInParent'); del.anchorScroll(animate); @@ -199,7 +196,8 @@ describe('anchorScroll', function() { }); it('should anchorScroll to top if element with hash id doesnt exist', function() { - var sv = del.getScrollView(scope); + var del = $ionicScrollDelegate(scope); + var sv = del.getScrollView(); spyOn(sv, 'scrollTo'); spyOn(ionic.DomUtil, 'getPositionInParent');