fix(jqLite): fix event.stopImmediatePropagation() so it works as expected

jqLite doesn't override the default implementation of event.stopImmediatePropagation()
and so it doesn't work as expected, i.e, it doesn't prevent the rest of the event
handlers from being executed.

Closes #4833
This commit is contained in:
Michael Benford
2013-11-08 00:38:04 -02:00
committed by Michał Gołębiowski
parent 19871d28cf
commit 30354c58fe
2 changed files with 110 additions and 6 deletions

View File

@@ -694,7 +694,6 @@ forEach({
function createEventHandler(element, events) {
var eventHandler = function (event, type) {
// jQuery specific api
event.isDefaultPrevented = function() {
return event.defaultPrevented;
@@ -705,13 +704,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);
}
}
};
@@ -912,11 +932,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
@@ -932,9 +953,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){

View File

@@ -1102,6 +1102,58 @@ describe('jqLite', function() {
browserTrigger(a, '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'),
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() {
var root, parent, sibling, child, log;
@@ -1784,7 +1836,6 @@ describe('jqLite', function() {
expect(event.isDefaultPrevented()).toBe(true);
});
it('should support handlers that deregister themselves', function() {
var element = jqLite('<a>poke</a>'),
clickSpy = jasmine.createSpy('click'),
@@ -1821,6 +1872,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);
});
});