]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
Dropdown — Emit events on the `.dropdown-toggle` button (#32625)
authorRohit Sharma <rohit2sharma95@gmail.com>
Wed, 3 Feb 2021 19:51:19 +0000 (01:21 +0530)
committerGitHub <noreply@github.com>
Wed, 3 Feb 2021 19:51:19 +0000 (21:51 +0200)
* Emit events on the dropdown button

Emit the events on `.dropdown-toggle` button and then bubble up

* Add migration note for events

* Update the docs for events

* Add unit test to check the event bubbling

Co-authored-by: XhmikosR <xhmikosr@gmail.com>
js/src/dropdown.js
js/tests/unit/dropdown.spec.js
site/content/docs/5.0/components/dropdowns.md
site/content/docs/5.0/migration.md

index 8fdf455801ed12327a94a3d4175f842ccbc39be1..69b99e628c985e4415cea2044a145e39df73033f 100644 (file)
@@ -194,7 +194,7 @@ class Dropdown extends BaseComponent {
 
     this._menu.classList.toggle(CLASS_NAME_SHOW)
     this._element.classList.toggle(CLASS_NAME_SHOW)
-    EventHandler.trigger(parent, EVENT_SHOWN, relatedTarget)
+    EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget)
   }
 
   hide() {
@@ -202,12 +202,11 @@ class Dropdown extends BaseComponent {
       return
     }
 
-    const parent = Dropdown.getParentFromElement(this._element)
     const relatedTarget = {
       relatedTarget: this._element
     }
 
-    const hideEvent = EventHandler.trigger(parent, EVENT_HIDE, relatedTarget)
+    const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget)
 
     if (hideEvent.defaultPrevented) {
       return
@@ -219,7 +218,7 @@ class Dropdown extends BaseComponent {
 
     this._menu.classList.toggle(CLASS_NAME_SHOW)
     this._element.classList.toggle(CLASS_NAME_SHOW)
-    EventHandler.trigger(parent, EVENT_HIDDEN, relatedTarget)
+    EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget)
   }
 
   dispose() {
@@ -383,7 +382,6 @@ class Dropdown extends BaseComponent {
     const toggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE)
 
     for (let i = 0, len = toggles.length; i < len; i++) {
-      const parent = Dropdown.getParentFromElement(toggles[i])
       const context = Data.getData(toggles[i], DATA_KEY)
       const relatedTarget = {
         relatedTarget: toggles[i]
@@ -409,7 +407,7 @@ class Dropdown extends BaseComponent {
         continue
       }
 
-      const hideEvent = EventHandler.trigger(parent, EVENT_HIDE, relatedTarget)
+      const hideEvent = EventHandler.trigger(toggles[i], EVENT_HIDE, relatedTarget)
       if (hideEvent.defaultPrevented) {
         continue
       }
@@ -429,7 +427,7 @@ class Dropdown extends BaseComponent {
 
       dropdownMenu.classList.remove(CLASS_NAME_SHOW)
       toggles[i].classList.remove(CLASS_NAME_SHOW)
-      EventHandler.trigger(parent, EVENT_HIDDEN, relatedTarget)
+      EventHandler.trigger(toggles[i], EVENT_HIDDEN, relatedTarget)
     }
   }
 
index 8b477ba38f8084263ac5c788b16d5ebd030d0489..bb137efed898b843f9a001c7797455d60f27c88b 100644 (file)
@@ -137,10 +137,9 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdownEl = fixtureEl.querySelector('.dropdown')
       const dropdown = new Dropdown(btnDropdown)
 
-      dropdownEl.addEventListener('shown.bs.dropdown', () => {
+      btnDropdown.addEventListener('shown.bs.dropdown', () => {
         expect(btnDropdown.classList.contains('show')).toEqual(true)
         expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
         done()
@@ -198,14 +197,13 @@ describe('Dropdown', () => {
 
       const defaultValueOnTouchStart = document.documentElement.ontouchstart
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdownEl = fixtureEl.querySelector('.dropdown')
       const dropdown = new Dropdown(btnDropdown)
 
       document.documentElement.ontouchstart = () => {}
       spyOn(EventHandler, 'on')
       spyOn(EventHandler, 'off')
 
-      dropdownEl.addEventListener('shown.bs.dropdown', () => {
+      btnDropdown.addEventListener('shown.bs.dropdown', () => {
         expect(btnDropdown.classList.contains('show')).toEqual(true)
         expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
         expect(EventHandler.on).toHaveBeenCalled()
@@ -213,7 +211,7 @@ describe('Dropdown', () => {
         dropdown.toggle()
       })
 
-      dropdownEl.addEventListener('hidden.bs.dropdown', () => {
+      btnDropdown.addEventListener('hidden.bs.dropdown', () => {
         expect(btnDropdown.classList.contains('show')).toEqual(false)
         expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')
         expect(EventHandler.off).toHaveBeenCalled()
@@ -236,10 +234,9 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdownEl = fixtureEl.querySelector('.dropdown')
       const dropdown = new Dropdown(btnDropdown)
 
-      dropdownEl.addEventListener('shown.bs.dropdown', () => {
+      btnDropdown.addEventListener('shown.bs.dropdown', () => {
         expect(btnDropdown.classList.contains('show')).toEqual(true)
         expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
         done()
@@ -351,12 +348,11 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdownEl = fixtureEl.querySelector('.dropdown')
       const dropdown = new Dropdown(btnDropdown, {
         reference: 'parent'
       })
 
-      dropdownEl.addEventListener('shown.bs.dropdown', () => {
+      btnDropdown.addEventListener('shown.bs.dropdown', () => {
         expect(btnDropdown.classList.contains('show')).toEqual(true)
         expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
         done()
@@ -376,12 +372,11 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdownEl = fixtureEl.querySelector('.dropdown')
       const dropdown = new Dropdown(btnDropdown, {
         reference: fixtureEl
       })
 
-      dropdownEl.addEventListener('shown.bs.dropdown', () => {
+      btnDropdown.addEventListener('shown.bs.dropdown', () => {
         expect(btnDropdown.classList.contains('show')).toEqual(true)
         expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
         done()
@@ -401,12 +396,11 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdownEl = fixtureEl.querySelector('.dropdown')
       const dropdown = new Dropdown(btnDropdown, {
         reference: { 0: fixtureEl, jquery: 'jQuery' }
       })
 
-      dropdownEl.addEventListener('shown.bs.dropdown', () => {
+      btnDropdown.addEventListener('shown.bs.dropdown', () => {
         expect(btnDropdown.classList.contains('show')).toEqual(true)
         expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
         done()
@@ -478,10 +472,9 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdownEl = fixtureEl.querySelector('.dropdown')
       const dropdown = new Dropdown(btnDropdown)
 
-      dropdownEl.addEventListener('shown.bs.dropdown', () => {
+      btnDropdown.addEventListener('shown.bs.dropdown', () => {
         throw new Error('should not throw shown.bs.dropdown event')
       })
 
@@ -504,10 +497,9 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdownEl = fixtureEl.querySelector('.dropdown')
       const dropdown = new Dropdown(btnDropdown)
 
-      dropdownEl.addEventListener('shown.bs.dropdown', () => {
+      btnDropdown.addEventListener('shown.bs.dropdown', () => {
         throw new Error('should not throw shown.bs.dropdown event')
       })
 
@@ -530,10 +522,9 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdownEl = fixtureEl.querySelector('.dropdown')
       const dropdown = new Dropdown(btnDropdown)
 
-      dropdownEl.addEventListener('shown.bs.dropdown', () => {
+      btnDropdown.addEventListener('shown.bs.dropdown', () => {
         throw new Error('should not throw shown.bs.dropdown event')
       })
 
@@ -556,14 +547,13 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdownEl = fixtureEl.querySelector('.dropdown')
       const dropdown = new Dropdown(btnDropdown)
 
-      dropdownEl.addEventListener('show.bs.dropdown', e => {
+      btnDropdown.addEventListener('show.bs.dropdown', e => {
         e.preventDefault()
       })
 
-      dropdownEl.addEventListener('shown.bs.dropdown', () => {
+      btnDropdown.addEventListener('shown.bs.dropdown', () => {
         throw new Error('should not throw shown.bs.dropdown event')
       })
 
@@ -588,10 +578,9 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdownEl = fixtureEl.querySelector('.dropdown')
       const dropdown = new Dropdown(btnDropdown)
 
-      dropdownEl.addEventListener('shown.bs.dropdown', () => {
+      btnDropdown.addEventListener('shown.bs.dropdown', () => {
         expect(btnDropdown.classList.contains('show')).toEqual(true)
         done()
       })
@@ -610,10 +599,9 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdownEl = fixtureEl.querySelector('.dropdown')
       const dropdown = new Dropdown(btnDropdown)
 
-      dropdownEl.addEventListener('shown.bs.dropdown', () => {
+      btnDropdown.addEventListener('shown.bs.dropdown', () => {
         throw new Error('should not throw shown.bs.dropdown event')
       })
 
@@ -636,10 +624,9 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdownEl = fixtureEl.querySelector('.dropdown')
       const dropdown = new Dropdown(btnDropdown)
 
-      dropdownEl.addEventListener('shown.bs.dropdown', () => {
+      btnDropdown.addEventListener('shown.bs.dropdown', () => {
         throw new Error('should not throw shown.bs.dropdown event')
       })
 
@@ -662,10 +649,9 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdownEl = fixtureEl.querySelector('.dropdown')
       const dropdown = new Dropdown(btnDropdown)
 
-      dropdownEl.addEventListener('shown.bs.dropdown', () => {
+      btnDropdown.addEventListener('shown.bs.dropdown', () => {
         throw new Error('should not throw shown.bs.dropdown event')
       })
 
@@ -688,14 +674,13 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdownEl = fixtureEl.querySelector('.dropdown')
       const dropdown = new Dropdown(btnDropdown)
 
-      dropdownEl.addEventListener('show.bs.dropdown', e => {
+      btnDropdown.addEventListener('show.bs.dropdown', e => {
         e.preventDefault()
       })
 
-      dropdownEl.addEventListener('shown.bs.dropdown', () => {
+      btnDropdown.addEventListener('shown.bs.dropdown', () => {
         throw new Error('should not throw shown.bs.dropdown event')
       })
 
@@ -720,11 +705,10 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdownEl = fixtureEl.querySelector('.dropdown')
       const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
       const dropdown = new Dropdown(btnDropdown)
 
-      dropdownEl.addEventListener('hidden.bs.dropdown', () => {
+      btnDropdown.addEventListener('hidden.bs.dropdown', () => {
         expect(dropdownMenu.classList.contains('show')).toEqual(false)
         done()
       })
@@ -743,15 +727,14 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdownEl = fixtureEl.querySelector('.dropdown')
       const dropdown = new Dropdown(btnDropdown)
 
-      dropdownEl.addEventListener('shown.bs.dropdown', () => {
+      btnDropdown.addEventListener('shown.bs.dropdown', () => {
         spyOn(dropdown._popper, 'destroy')
         dropdown.hide()
       })
 
-      dropdownEl.addEventListener('hidden.bs.dropdown', () => {
+      btnDropdown.addEventListener('hidden.bs.dropdown', () => {
         expect(dropdown._popper.destroy).toHaveBeenCalled()
         done()
       })
@@ -770,11 +753,10 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdownEl = fixtureEl.querySelector('.dropdown')
       const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
       const dropdown = new Dropdown(btnDropdown)
 
-      dropdownEl.addEventListener('hidden.bs.dropdown', () => {
+      btnDropdown.addEventListener('hidden.bs.dropdown', () => {
         throw new Error('should not throw hidden.bs.dropdown event')
       })
 
@@ -797,11 +779,10 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdownEl = fixtureEl.querySelector('.dropdown')
       const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
       const dropdown = new Dropdown(btnDropdown)
 
-      dropdownEl.addEventListener('hidden.bs.dropdown', () => {
+      btnDropdown.addEventListener('hidden.bs.dropdown', () => {
         throw new Error('should not throw hidden.bs.dropdown event')
       })
 
@@ -824,10 +805,9 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdownEl = fixtureEl.querySelector('.dropdown')
       const dropdown = new Dropdown(btnDropdown)
 
-      dropdownEl.addEventListener('hidden.bs.dropdown', () => {
+      btnDropdown.addEventListener('hidden.bs.dropdown', () => {
         throw new Error('should not throw hidden.bs.dropdown event')
       })
 
@@ -850,15 +830,14 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdownEl = fixtureEl.querySelector('.dropdown')
       const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
       const dropdown = new Dropdown(btnDropdown)
 
-      dropdownEl.addEventListener('hide.bs.dropdown', e => {
+      btnDropdown.addEventListener('hide.bs.dropdown', e => {
         e.preventDefault()
       })
 
-      dropdownEl.addEventListener('hidden.bs.dropdown', () => {
+      btnDropdown.addEventListener('hidden.bs.dropdown', () => {
         throw new Error('should not throw hidden.bs.dropdown event')
       })
 
@@ -983,15 +962,14 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdownEl = fixtureEl.querySelector('.dropdown')
       let showEventTriggered = false
       let hideEventTriggered = false
 
-      dropdownEl.addEventListener('show.bs.dropdown', () => {
+      btnDropdown.addEventListener('show.bs.dropdown', () => {
         showEventTriggered = true
       })
 
-      dropdownEl.addEventListener('shown.bs.dropdown', e => {
+      btnDropdown.addEventListener('shown.bs.dropdown', e => {
         expect(btnDropdown.classList.contains('show')).toEqual(true)
         expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
         expect(showEventTriggered).toEqual(true)
@@ -999,11 +977,11 @@ describe('Dropdown', () => {
         document.body.click()
       })
 
-      dropdownEl.addEventListener('hide.bs.dropdown', () => {
+      btnDropdown.addEventListener('hide.bs.dropdown', () => {
         hideEventTriggered = true
       })
 
-      dropdownEl.addEventListener('hidden.bs.dropdown', e => {
+      btnDropdown.addEventListener('hidden.bs.dropdown', e => {
         expect(btnDropdown.classList.contains('show')).toEqual(false)
         expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')
         expect(hideEventTriggered).toEqual(true)
@@ -1027,10 +1005,9 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdownEl = fixtureEl.querySelector('.dropdown')
       const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
 
-      dropdownEl.addEventListener('shown.bs.dropdown', () => {
+      btnDropdown.addEventListener('shown.bs.dropdown', () => {
         expect(dropdownMenu.getAttribute('style')).toEqual(null, 'no inline style applied by Popper')
         done()
       })
@@ -1049,10 +1026,9 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdownEl = fixtureEl.querySelector('.dropdown')
       const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
 
-      dropdownEl.addEventListener('shown.bs.dropdown', () => {
+      btnDropdown.addEventListener('shown.bs.dropdown', () => {
         // Popper adds this attribute when we use it
         expect(dropdownMenu.getAttribute('x-placement')).toEqual(null)
         done()
@@ -1072,9 +1048,8 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdownEl = fixtureEl.querySelector('.dropdown')
 
-      dropdownEl.addEventListener('shown.bs.dropdown', () => {
+      btnDropdown.addEventListener('shown.bs.dropdown', () => {
         expect(btnDropdown.classList.contains('show')).toEqual(true)
 
         const keyup = createEvent('keyup')
@@ -1083,7 +1058,7 @@ describe('Dropdown', () => {
         document.dispatchEvent(keyup)
       })
 
-      dropdownEl.addEventListener('hidden.bs.dropdown', () => {
+      btnDropdown.addEventListener('hidden.bs.dropdown', () => {
         expect(btnDropdown.classList.contains('show')).toEqual(false)
         done()
       })
@@ -1114,34 +1089,31 @@ describe('Dropdown', () => {
 
       expect(triggerDropdownList.length).toEqual(2)
 
-      const first = triggerDropdownList[0]
-      const last = triggerDropdownList[1]
-      const dropdownTestMenu = first.parentNode
-      const btnGroup = last.parentNode
+      const [triggerDropdownFirst, triggerDropdownLast] = triggerDropdownList
 
-      dropdownTestMenu.addEventListener('shown.bs.dropdown', () => {
-        expect(first.classList.contains('show')).toEqual(true)
+      triggerDropdownFirst.addEventListener('shown.bs.dropdown', () => {
+        expect(triggerDropdownFirst.classList.contains('show')).toEqual(true)
         expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1)
         document.body.click()
       })
 
-      dropdownTestMenu.addEventListener('hidden.bs.dropdown', () => {
+      triggerDropdownFirst.addEventListener('hidden.bs.dropdown', () => {
         expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(0)
-        last.click()
+        triggerDropdownLast.click()
       })
 
-      btnGroup.addEventListener('shown.bs.dropdown', () => {
-        expect(last.classList.contains('show')).toEqual(true)
+      triggerDropdownLast.addEventListener('shown.bs.dropdown', () => {
+        expect(triggerDropdownLast.classList.contains('show')).toEqual(true)
         expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1)
         document.body.click()
       })
 
-      btnGroup.addEventListener('hidden.bs.dropdown', () => {
+      triggerDropdownLast.addEventListener('hidden.bs.dropdown', () => {
         expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(0)
         done()
       })
 
-      first.click()
+      triggerDropdownFirst.click()
     })
 
     it('should remove "show" class if body if tabbing outside of menu, with multiple dropdowns', done => {
@@ -1165,13 +1137,10 @@ describe('Dropdown', () => {
 
       expect(triggerDropdownList.length).toEqual(2)
 
-      const first = triggerDropdownList[0]
-      const last = triggerDropdownList[1]
-      const dropdownTestMenu = first.parentNode
-      const btnGroup = last.parentNode
+      const [triggerDropdownFirst, triggerDropdownLast] = triggerDropdownList
 
-      dropdownTestMenu.addEventListener('shown.bs.dropdown', () => {
-        expect(first.classList.contains('show')).toEqual(true, '"show" class added on click')
+      triggerDropdownFirst.addEventListener('shown.bs.dropdown', () => {
+        expect(triggerDropdownFirst.classList.contains('show')).toEqual(true, '"show" class added on click')
         expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1, 'only one dropdown is shown')
 
         const keyup = createEvent('keyup')
@@ -1180,13 +1149,13 @@ describe('Dropdown', () => {
         document.dispatchEvent(keyup)
       })
 
-      dropdownTestMenu.addEventListener('hidden.bs.dropdown', () => {
+      triggerDropdownFirst.addEventListener('hidden.bs.dropdown', () => {
         expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(0, '"show" class removed')
-        last.click()
+        triggerDropdownLast.click()
       })
 
-      btnGroup.addEventListener('shown.bs.dropdown', () => {
-        expect(last.classList.contains('show')).toEqual(true, '"show" class added on click')
+      triggerDropdownLast.addEventListener('shown.bs.dropdown', () => {
+        expect(triggerDropdownLast.classList.contains('show')).toEqual(true, '"show" class added on click')
         expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1, 'only one dropdown is shown')
 
         const keyup = createEvent('keyup')
@@ -1195,12 +1164,12 @@ describe('Dropdown', () => {
         document.dispatchEvent(keyup)
       })
 
-      btnGroup.addEventListener('hidden.bs.dropdown', () => {
+      triggerDropdownLast.addEventListener('hidden.bs.dropdown', () => {
         expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(0, '"show" class removed')
         done()
       })
 
-      first.click()
+      triggerDropdownFirst.click()
     })
 
     it('should fire hide and hidden event without a clickEvent if event type is not click', done => {
@@ -1214,18 +1183,17 @@ describe('Dropdown', () => {
       ].join('')
 
       const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdown = fixtureEl.querySelector('.dropdown')
 
-      dropdown.addEventListener('hide.bs.dropdown', e => {
+      triggerDropdown.addEventListener('hide.bs.dropdown', e => {
         expect(e.clickEvent).toBeUndefined()
       })
 
-      dropdown.addEventListener('hidden.bs.dropdown', e => {
+      triggerDropdown.addEventListener('hidden.bs.dropdown', e => {
         expect(e.clickEvent).toBeUndefined()
         done()
       })
 
-      dropdown.addEventListener('shown.bs.dropdown', () => {
+      triggerDropdown.addEventListener('shown.bs.dropdown', () => {
         const keydown = createEvent('keydown')
 
         keydown.key = 'Escape'
@@ -1235,6 +1203,42 @@ describe('Dropdown', () => {
       triggerDropdown.click()
     })
 
+    it('should bubble up the events to the parent elements', done => {
+      fixtureEl.innerHTML = [
+        '<div class="dropdown">',
+        '  <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+        '  <div class="dropdown-menu">',
+        '    <a class="dropdown-item" href="#subMenu">Sub menu</a>',
+        '  </div>',
+        '</div>'
+      ].join('')
+
+      const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+      const dropdownParent = fixtureEl.querySelector('.dropdown')
+      const dropdown = new Dropdown(triggerDropdown)
+
+      const showFunction = jasmine.createSpy('showFunction')
+      dropdownParent.addEventListener('show.bs.dropdown', showFunction)
+
+      const shownFunction = jasmine.createSpy('shownFunction')
+      dropdownParent.addEventListener('shown.bs.dropdown', () => {
+        shownFunction()
+        dropdown.hide()
+      })
+
+      const hideFunction = jasmine.createSpy('hideFunction')
+      dropdownParent.addEventListener('hide.bs.dropdown', hideFunction)
+
+      dropdownParent.addEventListener('hidden.bs.dropdown', () => {
+        expect(showFunction).toHaveBeenCalled()
+        expect(shownFunction).toHaveBeenCalled()
+        expect(hideFunction).toHaveBeenCalled()
+        done()
+      })
+
+      dropdown.show()
+    })
+
     it('should ignore keyboard events within <input>s and <textarea>s', done => {
       fixtureEl.innerHTML = [
         '<div class="dropdown">',
@@ -1248,11 +1252,10 @@ describe('Dropdown', () => {
       ].join('')
 
       const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdown = fixtureEl.querySelector('.dropdown')
       const input = fixtureEl.querySelector('input')
       const textarea = fixtureEl.querySelector('textarea')
 
-      dropdown.addEventListener('shown.bs.dropdown', () => {
+      triggerDropdown.addEventListener('shown.bs.dropdown', () => {
         input.focus()
         const keydown = createEvent('keydown')
 
@@ -1284,9 +1287,8 @@ describe('Dropdown', () => {
       ].join('')
 
       const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdown = fixtureEl.querySelector('.dropdown')
 
-      dropdown.addEventListener('shown.bs.dropdown', () => {
+      triggerDropdown.addEventListener('shown.bs.dropdown', () => {
         const keydown = createEvent('keydown')
         keydown.key = 'ArrowDown'
 
@@ -1320,9 +1322,8 @@ describe('Dropdown', () => {
       ].join('')
 
       const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdown = fixtureEl.querySelector('.dropdown')
 
-      dropdown.addEventListener('shown.bs.dropdown', () => {
+      triggerDropdown.addEventListener('shown.bs.dropdown', () => {
         const keydown = createEvent('keydown')
         keydown.key = 'ArrowDown'
 
@@ -1350,11 +1351,10 @@ describe('Dropdown', () => {
       ].join('')
 
       const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdown = fixtureEl.querySelector('.dropdown')
       const item1 = fixtureEl.querySelector('#item1')
       const item2 = fixtureEl.querySelector('#item2')
 
-      dropdown.addEventListener('shown.bs.dropdown', () => {
+      triggerDropdown.addEventListener('shown.bs.dropdown', () => {
         const keydownArrowDown = createEvent('keydown')
         keydownArrowDown.key = 'ArrowDown'
 
@@ -1388,10 +1388,9 @@ describe('Dropdown', () => {
       ].join('')
 
       const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdown = fixtureEl.querySelector('.dropdown')
       const item1 = fixtureEl.querySelector('#item1')
 
-      dropdown.addEventListener('shown.bs.dropdown', () => {
+      triggerDropdown.addEventListener('shown.bs.dropdown', () => {
         const keydown = createEvent('keydown')
         keydown.key = 'ArrowUp'
 
@@ -1415,7 +1414,6 @@ describe('Dropdown', () => {
       ].join('')
 
       const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdown = fixtureEl.querySelector('.dropdown')
       const input = fixtureEl.querySelector('input')
 
       input.addEventListener('click', () => {
@@ -1423,7 +1421,7 @@ describe('Dropdown', () => {
         done()
       })
 
-      dropdown.addEventListener('shown.bs.dropdown', () => {
+      triggerDropdown.addEventListener('shown.bs.dropdown', () => {
         expect(triggerDropdown.classList.contains('show')).toEqual(true, 'dropdown menu is shown')
         input.dispatchEvent(createEvent('click'))
       })
@@ -1442,7 +1440,6 @@ describe('Dropdown', () => {
       ].join('')
 
       const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdown = fixtureEl.querySelector('.dropdown')
       const textarea = fixtureEl.querySelector('textarea')
 
       textarea.addEventListener('click', () => {
@@ -1450,7 +1447,7 @@ describe('Dropdown', () => {
         done()
       })
 
-      dropdown.addEventListener('shown.bs.dropdown', () => {
+      triggerDropdown.addEventListener('shown.bs.dropdown', () => {
         expect(triggerDropdown.classList.contains('show')).toEqual(true, 'dropdown menu is shown')
         textarea.dispatchEvent(createEvent('click'))
       })
@@ -1471,7 +1468,6 @@ describe('Dropdown', () => {
       ].join('')
 
       const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropdown = fixtureEl.querySelector('.dropdown')
       const input = fixtureEl.querySelector('input')
       const textarea = fixtureEl.querySelector('textarea')
 
@@ -1487,7 +1483,7 @@ describe('Dropdown', () => {
       const keydownEscape = createEvent('keydown')
       keydownEscape.key = 'Escape'
 
-      dropdown.addEventListener('shown.bs.dropdown', () => {
+      triggerDropdown.addEventListener('shown.bs.dropdown', () => {
         // Key Space
         input.focus()
         input.dispatchEvent(keydownSpace)
index 97821758371995fb6f913fbc2a2b0cfe67ab5cec..4c80e863c05516c684452fac7bae84dbbf441ab9 100644 (file)
@@ -968,8 +968,7 @@ Note when `boundary` is set to any value other than `'scrollParent'`, the style
 
 ### Events
 
-All dropdown events are fired at the `.dropdown-menu`'s parent element and have a `relatedTarget` property, whose value is the toggling anchor element.
-`hide.bs.dropdown` and `hidden.bs.dropdown` events have a `clickEvent` property (only when the original Event type is `click`) that contains an Event Object for the click event.
+All dropdown events are fired at the toggling element and then bubbled up. So you can also add event listeners on the `.dropdown-menu`'s parent element. `hide.bs.dropdown` and `hidden.bs.dropdown` events have a `clickEvent` property (only when the original Event type is `click`) that contains an Event Object for the click event.
 
 <table class="table">
   <thead>
index 850e27ab619afba3a4edd13eb3108947f6c1884d..8f71fc218ec4c3fd869ebef4a86d87cd1b7b4c8a 100644 (file)
@@ -22,6 +22,7 @@ toc: true
 
 - Restored `offset` option for Dropdown, Popover and Tooltip plugins.
 - The default value for the `fallbackPlacements` is changed to `['top', 'right', 'bottom', 'left']` for better placement of popper elements.
+- All the events for the dropdown are now triggered on the dropdown toggle button and then bubbled up to the parent element.
 
 ## v5.0.0-beta1