diff --git a/src/jqLite.js b/src/jqLite.js index 73b76f930739..cdde2f1c0855 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -692,7 +692,6 @@ forEach({ function createEventHandler(element, events) { var eventHandler = function (event, type) { - // jQuery specific api event.isDefaultPrevented = function() { return event.defaultPrevented; @@ -703,13 +702,34 @@ function createEventHandler(element, events) { if (!eventFnsLength) return; + if (isUndefined(event.immediatePropagationStopped)) { + var originalStopImmediatePropagation = event.stopImmediatePropagation; + event.stopImmediatePropagation = function() { + event.immediatePropagationStopped = true; + + if (event.stopPropagation) { + event.stopPropagation(); + } + + if (originalStopImmediatePropagation) { + originalStopImmediatePropagation.call(event); + } + }; + } + + event.isImmediatePropagationStopped = function() { + return event.immediatePropagationStopped === true; + }; + // Copy event handlers in case event handlers array is modified during execution. if ((eventFnsLength > 1)) { eventFns = shallowCopy(eventFns); } for (var i = 0; i < eventFnsLength; i++) { - eventFns[i].call(element, event); + if (!event.isImmediatePropagationStopped()) { + eventFns[i].call(element, event); + } } }; @@ -910,11 +930,12 @@ forEach({ var eventFns = events && events[eventName]; if (eventFns) { - // Create a dummy event to pass to the handlers dummyEvent = { preventDefault: function() { this.defaultPrevented = true; }, isDefaultPrevented: function() { return this.defaultPrevented === true; }, + stopImmediatePropagation: function() { this.immediatePropagationStopped = true; }, + isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; }, stopPropagation: noop, type: eventName, target: element @@ -930,9 +951,10 @@ forEach({ handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent]; forEach(eventFnsCopy, function(fn) { - fn.apply(element, handlerArgs); + if (!dummyEvent.isImmediatePropagationStopped()) { + fn.apply(element, handlerArgs); + } }); - } } }, function(fn, name){ diff --git a/test/jqLiteSpec.js b/test/jqLiteSpec.js index 3be21571f808..9601c9afd6e9 100644 --- a/test/jqLiteSpec.js +++ b/test/jqLiteSpec.js @@ -1091,7 +1091,10 @@ describe('jqLite', function() { }); it('should have event.isDefaultPrevented method', function() { - jqLite(a).on('click', function(e) { + var element = jqLite(a), + clickSpy = jasmine.createSpy('clickSpy'); + + clickSpy.andCallFake(function(e) { expect(function() { expect(e.isDefaultPrevented()).toBe(false); e.preventDefault(); @@ -1099,7 +1102,62 @@ describe('jqLite', function() { }).not.toThrow(); }); + element.on('click', clickSpy); + browserTrigger(a, 'click'); + expect(clickSpy).toHaveBeenCalled(); + }); + + it('should stop triggering handlers when stopImmediatePropagation is called', function() { + var element = jqLite(a), + clickSpy1 = jasmine.createSpy('clickSpy1'), + clickSpy2 = jasmine.createSpy('clickSpy2').andCallFake(function(event) { event.stopImmediatePropagation(); }), + clickSpy3 = jasmine.createSpy('clickSpy3'), + clickSpy4 = jasmine.createSpy('clickSpy4'); + + element.on('click', clickSpy1); + element.on('click', clickSpy2); + element.on('click', clickSpy3); + element[0].addEventListener('click', clickSpy4); + + browserTrigger(element, 'click'); + + expect(clickSpy1).toHaveBeenCalled(); + expect(clickSpy2).toHaveBeenCalled(); + expect(clickSpy3).not.toHaveBeenCalled(); + expect(clickSpy4).not.toHaveBeenCalled(); + }); + + it('should execute stopPropagation when stopImmediatePropagation is called', function() { + var element = jqLite(a), + clickSpy = jasmine.createSpy('clickSpy'); + + clickSpy.andCallFake(function(event) { + spyOn(event, 'stopPropagation'); + event.stopImmediatePropagation(); + expect(event.stopPropagation).toHaveBeenCalled(); + }); + + element.on('click', clickSpy); + + browserTrigger(element, 'click'); + expect(clickSpy).toHaveBeenCalled(); + }); + + it('should have event.isImmediatePropagationStopped method', function() { + var element = jqLite(a), + clickSpy = jasmine.createSpy('clickSpy'); + + clickSpy.andCallFake(function(event) { + expect(event.isImmediatePropagationStopped()).toBe(false); + event.stopImmediatePropagation(); + expect(event.isImmediatePropagationStopped()).toBe(true); + }); + + element.on('click', clickSpy); + + browserTrigger(element, 'click'); + expect(clickSpy).toHaveBeenCalled(); }); describe('mouseenter-mouseleave', function() { @@ -1784,7 +1842,6 @@ describe('jqLite', function() { expect(event.isDefaultPrevented()).toBe(true); }); - it('should support handlers that deregister themselves', function() { var element = jqLite('poke'), clickSpy = jasmine.createSpy('click'), @@ -1821,6 +1878,37 @@ describe('jqLite', function() { expect(actualEvent.target).toEqual(element[0]); expect(actualEvent.type).toEqual('click'); }); + + it('should stop triggering handlers when stopImmediatePropagation is called', function () { + var element = jqLite(a), + clickSpy1 = jasmine.createSpy('clickSpy1'), + clickSpy2 = jasmine.createSpy('clickSpy2').andCallFake(function(event) { event.stopImmediatePropagation(); }), + clickSpy3 = jasmine.createSpy('clickSpy3'); + + element.on('click', clickSpy1); + element.on('click', clickSpy2); + element.on('click', clickSpy3); + + element.triggerHandler('click'); + + expect(clickSpy1).toHaveBeenCalled(); + expect(clickSpy2).toHaveBeenCalled(); + expect(clickSpy3).not.toHaveBeenCalled(); + }); + + it('should have event.isImmediatePropagationStopped method', function() { + var element = jqLite(a), + clickSpy = jasmine.createSpy('clickSpy'), + event; + + element.on('click', clickSpy); + element.triggerHandler('click'); + event = clickSpy.mostRecentCall.args[0]; + + expect(event.isImmediatePropagationStopped()).toBe(false); + event.stopImmediatePropagation(); + expect(event.isImmediatePropagationStopped()).toBe(true); + }); });