diff --git a/js/src/dom/eventHandler.js b/js/src/dom/eventHandler.js index 465cbbeacc..819f489ea1 100644 --- a/js/src/dom/eventHandler.js +++ b/js/src/dom/eventHandler.js @@ -170,13 +170,14 @@ const EventHandler = (() => { } } - function findHandler(events, handler) { + function findHandler(events, handler, delegationSelector = null) { for (const uid in events) { if (!Object.prototype.hasOwnProperty.call(events, uid)) { continue } - if (events[uid].originalHandler === handler) { + const event = events[uid] + if (event.originalHandler === handler && event.delegationSelector === delegationSelector) { return events[uid] } } @@ -184,16 +185,7 @@ const EventHandler = (() => { return null } - function addHandler(element, originalTypeEvent, handler, delegationFn, oneOff) { - if (typeof originalTypeEvent !== 'string' || (typeof element === 'undefined' || element === null)) { - return - } - - if (!handler) { - handler = delegationFn - delegationFn = null - } - + function normalizeParams(originalTypeEvent, handler, delegationFn) { const delegation = typeof handler === 'string' const originalHandler = delegation ? delegationFn : handler @@ -210,9 +202,24 @@ const EventHandler = (() => { typeEvent = originalTypeEvent } + return [delegation, originalHandler, typeEvent] + } + + function addHandler(element, originalTypeEvent, handler, delegationFn, oneOff) { + if (typeof originalTypeEvent !== 'string' || (typeof element === 'undefined' || element === null)) { + return + } + + if (!handler) { + handler = delegationFn + delegationFn = null + } + + const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn) + const events = getEvent(element) const handlers = events[typeEvent] || (events[typeEvent] = {}) - const previousFn = findHandler(handlers, originalHandler) + const previousFn = findHandler(handlers, originalHandler, delegation ? handler : null) if (previousFn) { previousFn.oneOff = previousFn.oneOff && oneOff @@ -222,22 +229,23 @@ const EventHandler = (() => { const uid = getUidEvent(originalHandler, originalTypeEvent.replace(namespaceRegex, '')) const fn = !delegation ? bootstrapHandler(element, handler) : bootstrapDelegationHandler(element, handler, delegationFn) - fn.isDelegation = delegation + fn.delegationSelector = delegation ? handler : null fn.originalHandler = originalHandler fn.oneOff = oneOff + fn.uidEvent = uid handlers[uid] = fn element.addEventListener(typeEvent, fn, delegation) } - function removeHandler(element, events, typeEvent, handler) { - const fn = findHandler(events[typeEvent], handler) + function removeHandler(element, events, typeEvent, handler, delegationSelector) { + const fn = findHandler(events[typeEvent], handler, delegationSelector) if (fn === null) { return } - element.removeEventListener(typeEvent, fn, fn.isDelegation) - delete events[typeEvent][uidEvent] + element.removeEventListener(typeEvent, fn, Boolean(delegationSelector)) + delete events[typeEvent][fn.uidEvent] } function removeNamespacedHandlers(element, events, typeEvent, namespace) { @@ -248,7 +256,8 @@ const EventHandler = (() => { } if (handlerKey.indexOf(namespace) > -1) { - removeHandler(element, events, typeEvent, storeElementEvent[handlerKey].originalHandler) + const event = storeElementEvent[handlerKey] + removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector) } } } @@ -262,33 +271,23 @@ const EventHandler = (() => { addHandler(element, event, handler, delegationFn, true) }, - off(element, originalTypeEvent, handler) { - if (typeof originalTypeEvent !== 'string' || - (typeof element === 'undefined' || element === null)) { + off(element, originalTypeEvent, handler, delegationFn) { + if (typeof originalTypeEvent !== 'string' || (typeof element === 'undefined' || element === null)) { return } - const events = getEvent(element) - let typeEvent = originalTypeEvent.replace(stripNameRegex, '') + const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn) const inNamespace = typeEvent !== originalTypeEvent - const custom = customEvents[typeEvent] - if (custom) { - typeEvent = custom - } + const events = getEvent(element) - const isNative = nativeEvents.indexOf(typeEvent) > -1 - if (!isNative) { - typeEvent = originalTypeEvent - } - - if (typeof handler !== 'undefined') { + if (typeof originalHandler !== 'undefined') { // Simplest case: handler is passed, remove that listener ONLY. if (!events || !events[typeEvent]) { return } - removeHandler(element, events, typeEvent, handler) + removeHandler(element, events, typeEvent, originalHandler, delegation ? handler : null) return } @@ -311,7 +310,8 @@ const EventHandler = (() => { const handlerKey = keyHandlers.replace(stripUidRegex, '') if (!inNamespace || originalTypeEvent.indexOf(handlerKey) > -1) { - removeHandler(element, events, typeEvent, storeElementEvent[keyHandlers].originalHandler) + const event = storeElementEvent[keyHandlers] + removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector) } } }, diff --git a/js/tests/unit/dom/eventHandler.js b/js/tests/unit/dom/eventHandler.js index 7404d6c32b..998132911e 100644 --- a/js/tests/unit/dom/eventHandler.js +++ b/js/tests/unit/dom/eventHandler.js @@ -135,6 +135,32 @@ $(function () { document.body.removeChild(element) }) + QUnit.test('on should add delegated event listener if delegated selector differs', function (assert) { + assert.expect(1) + + var element = document.createElement('div') + var subelement = document.createElement('span') + element.appendChild(subelement) + + var anchor = document.createElement('a') + element.appendChild(anchor) + + var i = 0 + var handler = function () { + i++ + } + + EventHandler.on(element, 'click', 'a', handler) + EventHandler.on(element, 'click', 'span', handler) + + document.body.appendChild(element) + EventHandler.trigger(anchor, 'click') + EventHandler.trigger(subelement, 'click') + document.body.removeChild(element) + + assert.ok(i === 2, 'listeners called') + }) + QUnit.test('one should remove the listener after the event', function (assert) { assert.expect(1) @@ -268,4 +294,48 @@ $(function () { EventHandler.trigger(element, 'foobar') }) + + QUnit.test('off should remove the correct delegated event listener', function (assert) { + assert.expect(5) + + var element = document.createElement('div') + var subelement = document.createElement('span') + element.appendChild(subelement) + + var anchor = document.createElement('a') + element.appendChild(anchor) + + var i = 0 + var handler = function () { + i++ + } + + EventHandler.on(element, 'click', 'a', handler) + EventHandler.on(element, 'click', 'span', handler) + + document.body.appendChild(element) + + EventHandler.trigger(anchor, 'click') + EventHandler.trigger(subelement, 'click') + assert.ok(i === 2, 'first listeners called') + + EventHandler.off(element, 'click', 'span', handler) + EventHandler.trigger(subelement, 'click') + assert.ok(i === 2, 'removed listener not called') + + EventHandler.trigger(anchor, 'click') + assert.ok(i === 3, 'not removed listener called') + + EventHandler.on(element, 'click', 'span', handler) + EventHandler.trigger(anchor, 'click') + EventHandler.trigger(subelement, 'click') + assert.ok(i === 5, 'listener re-registered') + + EventHandler.off(element, 'click', 'span') + EventHandler.trigger(subelement, 'click') + assert.ok(i === 5, 'listener removed again') + + document.body.removeChild(element) + + }) })