From 27e9340b3c25b512e45213b39811098d07e12e3b Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Thu, 7 Nov 2013 16:19:03 -0800 Subject: feat(jqLite): expose isolateScope() getter similar to scope() See doc update in the diff for more info. BREAKING CHANGE: jqLite#scope() does not return the isolate scope on the element that triggered directive with isolate scope. Use jqLite#isolateScope() instead. --- src/Angular.js | 1 + src/jqLite.js | 17 ++++++- src/ng/compile.js | 12 ++++- test/jqLiteSpec.js | 34 ++++++++++++++ test/ng/compileSpec.js | 120 ++++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 178 insertions(+), 6 deletions(-) diff --git a/src/Angular.js b/src/Angular.js index 55a9bd8d..93fccd04 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -1244,6 +1244,7 @@ function bindJQuery() { jqLite = jQuery; extend(jQuery.fn, { scope: JQLitePrototype.scope, + isolateScope: JQLitePrototype.isolateScope, controller: JQLitePrototype.controller, injector: JQLitePrototype.injector, inheritedData: JQLitePrototype.inheritedData diff --git a/src/jqLite.js b/src/jqLite.js index 727218a9..218efe24 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -85,6 +85,9 @@ * - `injector()` - retrieves the injector of the current element or its parent. * - `scope()` - retrieves the {@link api/ng.$rootScope.Scope scope} of the current * element or its parent. + * - `isolateScope()` - retrieves an isolate {@link api/ng.$rootScope.Scope scope} if one is attached directly to the + * current element. This getter should be used only on elements that contain a directive which starts a new isolate + * scope. Calling `scope()` on this element always returns the original non-isolate scope. * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top * parent element is reached. * @@ -344,9 +347,13 @@ function jqLiteInheritedData(element, name, value) { if(element[0].nodeType == 9) { element = element.find('html'); } + var names = isArray(name) ? name : [name]; while (element.length) { - if ((value = element.data(name)) !== undefined) return value; + + for (var i = 0, ii = names.length; i < ii; i++) { + if ((value = element.data(names[i])) !== undefined) return value; + } element = element.parent(); } } @@ -418,7 +425,13 @@ forEach({ inheritedData: jqLiteInheritedData, scope: function(element) { - return jqLiteInheritedData(element, '$scope'); + // Can't use jqLiteData here directly so we stay compatible with jQuery! + return jqLite(element).data('$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); + }, + + isolateScope: function(element) { + // Can't use jqLiteData here directly so we stay compatible with jQuery! + return jqLite(element).data('$isolateScope') || jqLite(element).data('$isolateScopeNoTemplate'); }, controller: jqLiteController , diff --git a/src/ng/compile.js b/src/ng/compile.js index 7513fc7e..2e3d7867 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -1368,7 +1368,15 @@ function $CompileProvider($provide) { var $linkNode = jqLite(linkNode); isolateScope = scope.$new(true); - $linkNode.data('$isolateScope', isolateScope); + + if (templateDirective && (templateDirective === newIsolateScopeDirective.$$originalDirective)) { + $linkNode.data('$isolateScope', isolateScope) ; + } else { + $linkNode.data('$isolateScopeNoTemplate', isolateScope); + } + + + safeAddClass($linkNode, 'ng-isolate-scope'); forEach(newIsolateScopeDirective.scope, function(definition, scopeName) { @@ -1600,7 +1608,7 @@ function $CompileProvider($provide) { origAsyncDirective = directives.shift(), // The fact that we have to copy and patch the directive seems wrong! derivedSyncDirective = extend({}, origAsyncDirective, { - templateUrl: null, transclude: null, replace: null + templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective }), templateUrl = (isFunction(origAsyncDirective.templateUrl)) ? origAsyncDirective.templateUrl($compileNode, tAttrs) diff --git a/test/jqLiteSpec.js b/test/jqLiteSpec.js index 24920273..02a17df8 100644 --- a/test/jqLiteSpec.js +++ b/test/jqLiteSpec.js @@ -151,6 +151,13 @@ describe('jqLite', function() { dealoc(element); }); + it('should retrieve isolate scope attached to the current element', function() { + var element = jqLite('foo'); + element.data('$isolateScope', scope); + expect(element.isolateScope()).toBe(scope); + dealoc(element); + }); + it('should retrieve scope attached to the html element if its requested on the document', function() { var doc = jqLite(document), @@ -182,6 +189,33 @@ describe('jqLite', function() { }); + describe('isolateScope', function() { + + it('should retrieve isolate scope attached to the current element', function() { + var element = jqLite('foo'); + element.data('$isolateScope', scope); + expect(element.isolateScope()).toBe(scope); + dealoc(element); + }); + + + it('should not walk up the dom to find scope', function() { + var element = jqLite(''); + var deepChild = jqLite(element[0].getElementsByTagName('b')[0]); + element.data('$isolateScope', scope); + expect(deepChild.isolateScope()).toBeUndefined(); + dealoc(element); + }); + + + it('should return undefined when no scope was found', function() { + var element = jqLite('
'); + expect(element.isolateScope()).toBeFalsy(); + dealoc(element); + }); + }); + + describe('injector', function() { it('should retrieve injector attached to the current element or its parent', function() { var template = jqLite('
'), diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index a61d50f2..5bdbad42 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -1375,7 +1375,7 @@ describe('$compile', function() { return function (scope, element) { iscope = scope; log(scope.$id); - expect(element.data('$isolateScope')).toBe(scope); + expect(element.data('$isolateScopeNoTemplate')).toBe(scope); }; } }; @@ -1522,7 +1522,7 @@ describe('$compile', function() { ); - it('should allow more one new scope directives per element, but directives should share' + + it('should allow more than one new scope directives per element, but directives should share' + 'the scope', inject( function($rootScope, $compile, log) { element = $compile('
')($rootScope); @@ -1554,6 +1554,120 @@ describe('$compile', function() { expect(log).toEqual('002'); }) ); + + + describe('scope()/isolate() scope getters', function() { + + describe('with no directives', function() { + + it('should return the scope of the parent node', inject( + function($rootScope, $compile) { + element = $compile('
')($rootScope); + expect(element.scope()).toBe($rootScope); + }) + ); + }); + + + describe('with new scope directives', function() { + + it('should return the new scope at the directive element', inject( + function($rootScope, $compile) { + element = $compile('
')($rootScope); + expect(element.scope().$parent).toBe($rootScope); + }) + ); + + + it('should return the new scope for children in the original template', inject( + function($rootScope, $compile) { + element = $compile('
')($rootScope); + expect(element.find('a').scope().$parent).toBe($rootScope); + }) + ); + + + it('should return the new scope for children in the directive template', inject( + function($rootScope, $compile, $httpBackend) { + $httpBackend.expect('GET', 'tscope.html').respond(''); + element = $compile('
')($rootScope); + $httpBackend.flush(); + expect(element.find('a').scope().$parent).toBe($rootScope); + }) + ); + }); + + + describe('with isolate scope directives', function() { + + it('should return the root scope for directives at the root element', inject( + function($rootScope, $compile) { + element = $compile('
')($rootScope); + expect(element.scope()).toBe($rootScope); + }) + ); + + + it('should return the non-isolate scope at the directive element', inject( + function($rootScope, $compile) { + var directiveElement; + element = $compile('
')($rootScope); + directiveElement = element.children(); + expect(directiveElement.scope()).toBe($rootScope); + expect(directiveElement.isolateScope().$parent).toBe($rootScope); + }) + ); + + + it('should return the isolate scope for children in the original template', inject( + function($rootScope, $compile) { + element = $compile('
')($rootScope); + expect(element.find('a').scope()).toBe($rootScope); //xx + }) + ); + + + it('should return the isolate scope for children in directive template', inject( + function($rootScope, $compile, $httpBackend) { + $httpBackend.expect('GET', 'tiscope.html').respond(''); + element = $compile('
')($rootScope); + expect(element.isolateScope()).toBeUndefined(); // this is the current behavior, not desired feature + $httpBackend.flush(); + expect(element.find('a').scope()).toBe(element.isolateScope()); + expect(element.isolateScope()).not.toBe($rootScope); + }) + ); + }); + + + describe('with isolate scope directives and directives that manually create a new scope', function() { + + it('should return the new scope at the directive element', inject( + function($rootScope, $compile) { + var directiveElement; + element = $compile('
')($rootScope); + $rootScope.$apply(); + directiveElement = element.find('a'); + expect(directiveElement.scope().$parent).toBe($rootScope); + expect(directiveElement.scope()).not.toBe(directiveElement.isolateScope()); + }) + ); + + + it('should return the isolate scope for child elements', inject( + function($rootScope, $compile, $httpBackend) { + var directiveElement, child; + $httpBackend.expect('GET', 'tiscope.html').respond(''); + element = $compile('
')($rootScope); + $rootScope.$apply(); + $httpBackend.flush(); + directiveElement = element.find('a'); + child = directiveElement.find('span'); + expect(child.scope()).toBe(directiveElement.isolateScope()); + }) + ); + }); + }); }); }); }); @@ -2121,6 +2235,7 @@ describe('$compile', function() { }); })); + it('should give other directives the parent scope', inject(function($rootScope) { compile('
'); $rootScope.$apply(function() { @@ -2131,6 +2246,7 @@ describe('$compile', function() { expect(componentScope.$parent).toBe(regularScope) })); + it('should not give the isolate scope to other directive template', function() { module(function() { directive('otherTplDir', function() { -- cgit v1.2.3