diff options
| -rw-r--r-- | src/Angular.js | 1 | ||||
| -rw-r--r-- | src/jqLite.js | 17 | ||||
| -rw-r--r-- | src/ng/compile.js | 12 | ||||
| -rw-r--r-- | test/jqLiteSpec.js | 34 | ||||
| -rwxr-xr-x | 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('<i>foo</i>'); +      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('<i>foo</i>'); +      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('<ul><li><p><b>deep deep</b><p></li></ul>'); +      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('<div></div>'); +      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('<div><span></span></div>'), 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('<div class="scope-a; scope-b"></div>')($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('<div></div>')($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('<div scope></div>')($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('<div scope><a></a></div>')($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('<a></a>'); +                element = $compile('<div tscope></div>')($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('<div iscope></div>')($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('<div><div iscope></div></div>')($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('<div iscope><a></a></div>')($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('<a></a>'); +                element = $compile('<div tiscope></div>')($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('<div><a ng-if="true" iscope></a></div>')($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('<span></span>'); +                element = $compile('<div><a ng-if="true" tiscope></a></div>')($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('<div><input type="text" my-component store-scope ng-model="value"></div>');        $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() { | 
