Skip to content

Commit

Permalink
feat(ionContent): use child scope instead of isolate scope
Browse files Browse the repository at this point in the history
Adds new '$ionicBind' service, which takes an object containing
binding definitions (similar to angular directive isolate scope
definition).  Allows binding of any directive attribute & expressions
from a scope, letting us do normal attribute -> scope binding
without having to create isolate scopes.

Closes #555. Closes #669
  • Loading branch information
ajoslin committed Feb 24, 2014
1 parent 9e74608 commit 49e0dac
Show file tree
Hide file tree
Showing 7 changed files with 268 additions and 35 deletions.
61 changes: 38 additions & 23 deletions js/ext/angular/src/directive/ionicContent.js
Expand Up @@ -18,33 +18,23 @@ angular.module('ionic.ui.content', ['ionic.ui.service', 'ionic.ui.scroll'])

// The content directive is a core scrollable content area
// that is part of many View hierarchies
.directive('ionContent', ['$parse', '$timeout', '$ionicScrollDelegate', '$controller', function($parse, $timeout, $ionicScrollDelegate, $controller) {
.directive('ionContent', [
'$parse',
'$timeout',
'$ionicScrollDelegate',
'$controller',
'$ionicBind',
function($parse, $timeout, $ionicScrollDelegate, $controller, $ionicBind) {
return {
restrict: 'E',
replace: true,
template: '<div class="scroll-content"><div class="scroll" ng-transclude></div></div>',
transclude: true,
require: '^?ionNavView',
scope: {
onRefresh: '&',
onRefreshOpening: '&',
onScroll: '&',
onScrollComplete: '&',
refreshComplete: '=',
onInfiniteScroll: '=',
infiniteScrollDistance: '@',
hasBouncing: '@',
scroll: '@',
padding: '@',
hasScrollX: '@',
hasScrollY: '@',
scrollbarX: '@',
scrollbarY: '@',
startX: '@',
startY: '@',
scrollEventInterval: '@'
},

scope: true,
template:
'<div class="scroll-content">' +
'<div class="scroll"></div>' +
'</div>',
compile: function(element, attr, transclude) {
if(attr.hasHeader == "true") { element.addClass('has-header'); }
if(attr.hasSubheader == "true") { element.addClass('has-subheader'); }
Expand All @@ -60,7 +50,31 @@ angular.module('ionic.ui.content', ['ionic.ui.service', 'ionic.ui.scroll'])

function prelink($scope, $element, $attr, navViewCtrl) {
var clone, sc, scrollView, scrollCtrl,
c = angular.element($element.children()[0]);
scrollContent = angular.element($element[0].querySelector('.scroll'));

transclude($scope, function(clone) {
scrollContent.append(clone);
});

$ionicBind($scope, $attr, {
onRefresh: '&',
onRefreshOpening: '&',
onScroll: '&',
onScrollComplete: '&',
refreshComplete: '=',
onInfiniteScroll: '&',
infiniteScrollDistance: '@',
hasBouncing: '@',
scroll: '@',
padding: '@',
hasScrollX: '@',
hasScrollY: '@',
scrollbarX: '@',
scrollbarY: '@',
startX: '@',
startY: '@',
scrollEventInterval: '@'
});

if($scope.scroll === "false") {
// No scrolling
Expand Down Expand Up @@ -92,6 +106,7 @@ angular.module('ionic.ui.content', ['ionic.ui.service', 'ionic.ui.scroll'])
}
}
});

//Publish scrollView to parent so children can access it
scrollView = $scope.$parent.scrollView = scrollCtrl.scrollView;

Expand Down
1 change: 1 addition & 0 deletions js/ext/angular/src/ionicAngular.js
Expand Up @@ -3,6 +3,7 @@
* modules.
*/
angular.module('ionic.service', [
'ionic.service.bind',
'ionic.service.platform',
'ionic.service.actionSheet',
'ionic.service.gesture',
Expand Down
53 changes: 53 additions & 0 deletions js/ext/angular/src/service/ionicBind.js
@@ -0,0 +1,53 @@
angular.module('ionic.service.bind', [])
.factory('$ionicBind', ['$parse', '$interpolate', function($parse, $interpolate) {
var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/;
return function(scope, attrs, bindDefinition) {
angular.forEach(bindDefinition || {}, function (definition, scopeName) {
//Adapted from angular.js $compile
var match = definition.match(LOCAL_REGEXP) || [],
attrName = match[3] || scopeName,
mode = match[1], // @, =, or &
parentGet,
unwatch;

switch(mode) {
case '@':
if (!attrs[attrName]) {
return;
}
attrs.$observe(attrName, function(value) {
scope[scopeName] = value;
});
// we trigger an interpolation to ensure
// the value is there for use immediately
if (attrs[attrName]) {
scope[scopeName] = $interpolate(attrs[attrName])(scope);
}
break;

case '=':
if (!attrs[attrName]) {
return;
}
unwatch = scope.$watch(attrs[attrName], function(value) {
scope[scopeName] = value;
});
//Destroy parent scope watcher when this scope is destroyed
scope.$on('$destroy', unwatch);
break;

case '&':
/* jshint -W044 */
if (attrs[attrName] && attrs[attrName].match(RegExp(scopeName + '\(.*?\)'))) {
throw new Error('& expression binding "' + scopeName + '" looks like it will recursively call "' +
attrs[attrName] + '" and cause a stack overflow! Please choose a different scopeName.');
}
parentGet = $parse(attrs[attrName]);
scope[scopeName] = function(locals) {
return parentGet(scope, locals);
};
break;
}
});
};
}]);
37 changes: 27 additions & 10 deletions js/ext/angular/test/directive/ionicContent.unit.js
@@ -1,5 +1,5 @@
describe('Ionic Content directive', function() {
var compile, element, scope;
var compile, scope;

beforeEach(module('ionic'));

Expand All @@ -12,22 +12,22 @@ describe('Ionic Content directive', function() {
}));

it('Has $ionicScroll controller', function() {
element = compile('<ion-content></ion-content>')(scope);
var element = compile('<ion-content></ion-content>')(scope);
expect(element.controller('$ionicScroll').element).toBe(element[0]);
});

it('Has content class', function() {
element = compile('<ion-content></ion-content>')(scope);
var element = compile('<ion-content></ion-content>')(scope);
expect(element.hasClass('scroll-content')).toBe(true);
});

it('Has header', function() {
element = compile('<ion-content has-header="true"></ion-content>')(scope);
var element = compile('<ion-content has-header="true"></ion-content>')(scope);
expect(element.hasClass('has-header')).toEqual(true);
});

it('should add padding classname', function() {
element = compile('<ion-content padding="true"></ion-content>')(scope);
var element = compile('<ion-content padding="true"></ion-content>')(scope);
expect(element.hasClass('scroll-content')).toEqual(true);
expect(element.hasClass('padding')).toEqual(false);
var scrollElement = element.find('.scroll');
Expand All @@ -36,7 +36,7 @@ describe('Ionic Content directive', function() {

// it('Enables bouncing by default', function() {
// ionic.Platform.setPlatform('iPhone');
// element = compile('<ion-content has-header="true"></ion-content>')(scope);
// var element = compile('<ion-content has-header="true"></ion-content>')(scope);
// scope.$apply();
// var newScope = element.isolateScope();
// var scrollView = scope.scrollView;
Expand All @@ -45,7 +45,7 @@ describe('Ionic Content directive', function() {

it('Disables bouncing when has-bouncing = false', function() {
ionic.Platform.setPlatform('iPhone');
element = compile('<ion-content has-header="true" has-bouncing="false"></ion-content>')(scope);
var element = compile('<ion-content has-header="true" has-bouncing="false"></ion-content>')(scope);
scope.$apply();
var newScope = element.isolateScope();
var scrollView = scope.scrollView;
Expand All @@ -54,7 +54,7 @@ describe('Ionic Content directive', function() {

it('Disables bouncing by default on Android', function() {
ionic.Platform.setPlatform('Android');
element = compile('<ion-content has-header="true"></ion-content>')(scope);
var element = compile('<ion-content has-header="true"></ion-content>')(scope);
scope.$apply();
var newScope = element.isolateScope();
var scrollView = scope.scrollView;
Expand All @@ -63,7 +63,7 @@ describe('Ionic Content directive', function() {

it('Disables bouncing by default on Android unless has-bouncing = true', function() {
ionic.Platform.setPlatform('Android');
element = compile('<ion-content has-header="true" has-bouncing="true"></ion-content>')(scope);
var element = compile('<ion-content has-header="true" has-bouncing="true"></ion-content>')(scope);
scope.$apply();
var newScope = element.isolateScope();
var scrollView = scope.scrollView;
Expand All @@ -72,7 +72,7 @@ describe('Ionic Content directive', function() {


it('Should set start x and y', function() {
element = compile('<ion-content start-x="100" start-y="300" has-header="true"></ion-content>')(scope);
var element = compile('<ion-content start-x="100" start-y="300" has-header="true"></ion-content>')(scope);
scope.$apply();
var newScope = element.isolateScope();
var scrollView = scope.scrollView;
Expand Down Expand Up @@ -139,3 +139,20 @@ describe('Ionic Content directive', function() {
});
});
});
/* Tests #555 */
describe('Ionic Content Directive scoping', function() {
beforeEach(module('ionic', function($controllerProvider) {
$controllerProvider.register('ContentTestCtrl', function($scope){
this.$scope = $scope;
});
}));
it('should have same scope as content', inject(function($compile, $rootScope) {
var element = $compile('<ion-content ng-controller="ContentTestCtrl">' +
'<form name="myForm"></form>' +
'</ion-content>')($rootScope.$new());
var contentScope = element.scope();
var ctrl = element.data('$ngControllerController');
expect(contentScope.myForm).toBeTruthy();
expect(ctrl.$scope.myForm).toBeTruthy();
}));
});
12 changes: 11 additions & 1 deletion js/ext/angular/test/list-fit.html
Expand Up @@ -14,7 +14,7 @@

<ion-header-bar title="'Sample UL'" type="bar-positive"></ion-header-bar>

<ion-content has-header="true" scroll="true" ng-controller="ContentCtrl" on-refresh="onRefresh()" has-footer="true" padding="false">
<ion-content on-infinite-scroll="addMore()" has-header="true" scroll="true" ng-controller="ContentCtrl" on-refresh="onRefresh()" has-footer="true" padding="false">

<ion-refresher></ion-refresher>

Expand Down Expand Up @@ -45,7 +45,11 @@
<li class="item">24</li>
<li class="item">25</li>
<li class="item">26</li>
<li ng-repeat="i in more">more {{$index}}</li>
</ul>

<ion-infinite-scroll></ion-infinite-scroll>

</ion-content>

<ion-footer-bar type="bar-assertive">
Expand All @@ -59,6 +63,12 @@ <h1 class="title">Footer!</h1>
$scope.$broadcast('scroll.refreshComplete');
}, 1000);
};
$scope.more = [];
$scope.addMore = function() {
for (var i=0; i<15; i++) {
$scope.more.push(i);
}
};
}
</script>

Expand Down
Expand Up @@ -34,7 +34,7 @@ describe('Ionic ScrollDelegate Service', function() {
it('scroll event', function() {
var scope = rootScope.$new();
var el = compile('<ion-content></ion-content>')(scope);
scope = el.isolateScope();
scope = el.scope();
scope.$apply();
var top, left;
scope.onScroll = jasmine.createSpy('scroll').andCallFake(function(data) {
Expand Down

0 comments on commit 49e0dac

Please sign in to comment.