refactor($ionicScrollDelegate): make it a factory from current scope

BREAKING CHANGE: $ionicScrollDelegate no longer works globally; you must
create a new instance of each time you use it.  The actual methods on
each instance of $ionicScrollDelegate are the same, however.

Change your code from this:

```js
function MyController($scope, $ionicScrollDelegate) {
  $scope.scrollTop = function() {
    $ionicScrollDelegate.scrollTop();
  };
}
```

To this:

```js
function MyController($scope, $ionicScrollDelegate) {
  var delegate = $ionicScrollDelegate($scope);
  $scope.scrollTop = function() {
    delegate.scrollTop();
  };
}
```
This commit is contained in:
Andy Joslin
2014-03-17 07:18:28 -06:00
parent f8a7137744
commit 4715a118e0
9 changed files with 237 additions and 189 deletions

View File

@@ -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();

View File

@@ -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 ?
'<header class="bar bar-header" ng-transclude></header>' :
'<footer class="bar bar-header" ng-transclude></footer>';
var BAR_MODEL_DEFAULT = isHeader ?
'headerBarController' :
'<footer class="bar bar-footer" ng-transclude></footer>';
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);
});
}
};
}];

View File

@@ -72,7 +72,7 @@ function($parse, $timeout, $ionicScrollDelegate, $controller, $ionicBind) {
require: '^?ionNavView',
scope: true,
template:
'<div class="scroll-content">' +
'<div class="scroll-content" ng-class="$$contentState.getClassName()">' +
'<div class="scroll"></div>' +
'</div>',
compile: function(element, attr, transclude) {

View File

@@ -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);