From ec69de4b7e6df400fb2f2796ae1a9898c017bcca Mon Sep 17 00:00:00 2001
From: Alessandro Chitolina <alekitto@gmail.com>
Date: Sat, 21 Oct 2017 01:38:59 +0200
Subject: [PATCH] allow register the same handler for different delegated
 selectors in eventHandler

---
 js/src/dom/eventHandler.js        | 72 +++++++++++++++----------------
 js/tests/unit/dom/eventHandler.js | 70 ++++++++++++++++++++++++++++++
 2 files changed, 106 insertions(+), 36 deletions(-)

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 isNative = nativeEvents.indexOf(typeEvent) > -1
-      if (!isNative) {
-        typeEvent = originalTypeEvent
-      }
+      const events = getEvent(element)
 
-      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)
+
+  })
 })
-- 
GitLab