diff options
| author | Vojta Jina | 2012-09-25 01:09:26 -0700 | 
|---|---|---|
| committer | Igor Minar | 2012-11-25 11:39:54 +0100 | 
| commit | e6966e05f508d1d2633b9ff327fea912b12555ac (patch) | |
| tree | eb7ff1052c75485a709e647cb8c550297746d573 | |
| parent | 682418f029cb9710a77eb377dd2a8390eaac4516 (diff) | |
| download | angular.js-e6966e05f508d1d2633b9ff327fea912b12555ac.tar.bz2 | |
fix(Scope): allow removing a listener during event
| -rw-r--r-- | src/ng/rootScope.js | 28 | ||||
| -rw-r--r-- | test/ng/rootScopeSpec.js | 68 | 
2 files changed, 91 insertions, 5 deletions
| diff --git a/src/ng/rootScope.js b/src/ng/rootScope.js index 5754ad32..de4b7407 100644 --- a/src/ng/rootScope.js +++ b/src/ng/rootScope.js @@ -638,7 +638,7 @@ function $RootScopeProvider(){          namedListeners.push(listener);          return function() { -          arrayRemove(namedListeners, listener); +          namedListeners[indexOf(namedListeners, listener)] = null;          };        }, @@ -686,6 +686,14 @@ function $RootScopeProvider(){            namedListeners = scope.$$listeners[name] || empty;            event.currentScope = scope;            for (i=0, length=namedListeners.length; i<length; i++) { + +            // if listeners were deregistered, defragment the array +            if (!namedListeners[i]) { +              namedListeners.splice(i, 1); +              i--; +              length--; +              continue; +            }              try {                namedListeners[i].apply(null, listenerArgs);                if (stopPropagation) return event; @@ -735,19 +743,29 @@ function $RootScopeProvider(){                },                defaultPrevented: false              }, -            listenerArgs = concat([event], arguments, 1); +            listenerArgs = concat([event], arguments, 1), +            listeners, i, length;          //down while you can, then up and next sibling or up and next sibling until back at root          do {            current = next;            event.currentScope = current; -          forEach(current.$$listeners[name], function(listener) { +          listeners = current.$$listeners[name] || []; +          for (i=0, length = listeners.length; i<length; i++) { +            // if listeners were deregistered, defragment the array +            if (!listeners[i]) { +              listeners.splice(i, 1); +              i--; +              length--; +              continue; +            } +              try { -              listener.apply(null, listenerArgs); +              listeners[i].apply(null, listenerArgs);              } catch(e) {                $exceptionHandler(e);              } -          }); +          }            // Insanity Warning: scope depth-first traversal            // yes, this code is a bit crazy, but it works and we have tests to prove it! diff --git a/test/ng/rootScopeSpec.js b/test/ng/rootScopeSpec.js index 9830d981..ee7fb796 100644 --- a/test/ng/rootScopeSpec.js +++ b/test/ng/rootScopeSpec.js @@ -708,6 +708,74 @@ describe('Scope', function() {        }); +      it('should allow removing event listener inside a listener on $emit', function() { +        var spy1 = jasmine.createSpy('1st listener'); +        var spy2 = jasmine.createSpy('2nd listener'); +        var spy3 = jasmine.createSpy('3rd listener'); + +        var remove1 = child.$on('evt', spy1); +        var remove2 = child.$on('evt', spy2); +        var remove3 = child.$on('evt', spy3); + +        spy1.andCallFake(remove1); + +        expect(child.$$listeners['evt'].length).toBe(3); + +        // should call all listeners and remove 1st +        child.$emit('evt'); +        expect(spy1).toHaveBeenCalledOnce(); +        expect(spy2).toHaveBeenCalledOnce(); +        expect(spy3).toHaveBeenCalledOnce(); +        expect(child.$$listeners['evt'].length).toBe(3); // cleanup will happen on next $emit + +        spy1.reset(); +        spy2.reset(); +        spy3.reset(); + +        // should call only 2nd because 1st was already removed and 2nd removes 3rd +        spy2.andCallFake(remove3); +        child.$emit('evt'); +        expect(spy1).not.toHaveBeenCalled(); +        expect(spy2).toHaveBeenCalledOnce(); +        expect(spy3).not.toHaveBeenCalled(); +        expect(child.$$listeners['evt'].length).toBe(1); +      }); + + +      it('should allow removing event listener inside a listener on $broadcast', function() { +        var spy1 = jasmine.createSpy('1st listener'); +        var spy2 = jasmine.createSpy('2nd listener'); +        var spy3 = jasmine.createSpy('3rd listener'); + +        var remove1 = child.$on('evt', spy1); +        var remove2 = child.$on('evt', spy2); +        var remove3 = child.$on('evt', spy3); + +        spy1.andCallFake(remove1); + +        expect(child.$$listeners['evt'].length).toBe(3); + +        // should call all listeners and remove 1st +        child.$broadcast('evt'); +        expect(spy1).toHaveBeenCalledOnce(); +        expect(spy2).toHaveBeenCalledOnce(); +        expect(spy3).toHaveBeenCalledOnce(); +        expect(child.$$listeners['evt'].length).toBe(3); //cleanup will happen on next $broadcast + +        spy1.reset(); +        spy2.reset(); +        spy3.reset(); + +        // should call only 2nd because 1st was already removed and 2nd removes 3rd +        spy2.andCallFake(remove3); +        child.$broadcast('evt'); +        expect(spy1).not.toHaveBeenCalled(); +        expect(spy2).toHaveBeenCalledOnce(); +        expect(spy3).not.toHaveBeenCalled(); +        expect(child.$$listeners['evt'].length).toBe(1); +      }); + +        describe('event object', function() {          it('should have methods/properties', function() {            var event; | 
