javascripttwitter-bootstrapdrop-down-menubootstrap-5nav

How to activate bootstrap dropdown menu item by pressing keyboard key


Bootstrap 5 navbar contains dropdown menus.
Dropdown menus have underlined hotkeys like a,n,s,e :

<div class="btn-group">
  <button type="button" class="btn btn-danger dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
    Action
  </button>
  <ul class="dropdown-menu">
    <li><a class="dropdown-item" href="#"><u>A</u>ction</a></li>
    <li><a class="dropdown-item" href="#">A<u>n</u>other action</a></li>
    <li><a class="dropdown-item" href="#"><u>S</u>omething else here</a></li>
    <li><hr class="dropdown-divider"></li>
    <li><a class="dropdown-item" href="#">S<u>e</u>parated link</a></li>
  </ul>
</div>

How to activate menu item if key is pressed in keyboard? For example, if dropdowns menu is open, pressing A should act like Action menu item is clicked.

jsfiddle: https://jsfiddle.net/b6or2s5e/

Left and right arrows are already handled using

How to use left and right arrow keys to navigate in bootstrap navbar

document.addEventListener("DOMContentLoaded", function () {
  document.querySelectorAll('.dropdown').forEach(function (el) {
    el.addEventListener('shown.bs.dropdown', function () {
      document.addEventListener('keydown', function (e) {
        const click = new MouseEvent("click", {
          bubbles: true,
          cancelable: true,
          view: window,
        })
        if (e.key === "ArrowRight") {
          const o = getNextSibling(e.target.closest('.dropdown'), '.dropdown')
          if (!o) {
            document.querySelector('.dropdown-toggle').dispatchEvent(click)
          } else {
            o.querySelector('.Xdropdown-toggle').dispatchEvent(click)
          }
        } else if (e.key === "ArrowLeft") {
          const o = getPrevSibling(e.target.closest('.dropdown'), '.dropdown')
          if (!o) {
            const ar = document.querySelectorAll('.dropdown-toggle')
            ar[ar.length - 1].dispatchEvent(click)
          } else {
            o.querySelector('.dropdown-toggle').dispatchEvent(click)
          }
        }
      }, { once: true })
    })
  })
})

How to add hotkey handling also?


Solution

  • Here is an updated snippet with a dynamic approach by getting the underlined elements and using a forEach loop over the underlined elements nodelist to compare a keydown event on the window with each underlined elements textContent converted to toLowerCase() and then compared. If there is a match we run click() on the matching element.

    Can switch switch statement removed and findUnderlined called for any key if alt or ctrl is not pressed?

    I believe you are asking if you can add control and/or alt keys, not 100% sure of your ask is here, but you can add any conditional in terms of what you want to control with keydown. Just add the logic to the conditional.

    See further notes in snippet below...

    document.addEventListener("DOMContentLoaded", function() {
      // get drop-down menu
      const dropdownMenu = document.querySelector('.dropdown-menu');
      // get action button
      const action = document.querySelector('#dropdown-btn');
      // get the underlined elements as a nodelist
      const sections = dropdownMenu.querySelectorAll('li u');
      // helper function pass in underlined els and event key
      const setClickForUnderlinedEls = (els, key) => {
        // loop over underlined elements
        els.forEach(sec => {
          // conditional to compare the underlined 
          // elements text to lowercase
          if (key === sec.textContent.toLowerCase()) {
            // simulate a click on element
            sec.click()
          }
        });
      }
      // event click on action button to open dropdown
      action.addEventListener('click', (event) => {
        // window event on keydown to check key
        addEventListener('keydown', (e) => {
          // make sure the dropdown is expanded 
          // using aria-expanded attribute
          if (event.target.ariaExpanded !== 'false') {
            // run helper function to compare the key and
            // the underlined elements textContent
            setClickForUnderlinedEls(sections, e.key);
          }
        });
      })
    });
    

    document.addEventListener("DOMContentLoaded", function() {
      const dropdownMenu = document.querySelector('.dropdown-menu');
      const action = document.querySelector('#dropdown-btn');
      const sections = dropdownMenu.querySelectorAll('li u');
      const setClickForUnderlinedEls = (els, key) => {
        els.forEach(sec => {
          if (key === sec.textContent.toLowerCase()) {
            sec.click()
          }
        });
      }
      action.addEventListener('click', (event) => {
        addEventListener('keydown', (e) => {
          if (event.target.ariaExpanded !== 'false') {
            setClickForUnderlinedEls(sections, e.key);
          }
        });
      })
    });
    #vertical-space,
    #section1,
    #section2,
    #section3,
    #section4,
    #section5{
      height: 100vh;
    }
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
    
    <!-- Example single danger button -->
    <div class="btn-group" id="top">
      <!--/ Added the id dropdown-btn for this example /-->
      <button id="dropdown-btn" type="button" class="btn btn-danger dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
        Action
      </button>
      <ul class="dropdown-menu">
        <li><a class="dropdown-item" href="#section1"><u>A</u>ction</a></li>
        <li><a class="dropdown-item" href="#section2">A<u>n</u>other action</a></li>
        <li><a class="dropdown-item" href="#section3"><u>W</u>e have a link</a></li>
        <li><a class="dropdown-item" href="#section4"><u>S</u>omething else here</a></li>
        <li>
          <hr class="dropdown-divider">
        </li>
        <li><a class="dropdown-item" href="#section5">S<u>e</u>parated link</a></li>
      </ul>
    </div>
    <!--/ added purely for example /-->
    <div id="vertical-space">
    </div>
    <div id="section1">
      <p>
        You pressed the <strong>a</strong> button and instantiated a click on the <em>Action</em> button. <a href="#top">Back to top</a>
      </p>
      
    </div>
    <div id="section2">
      <p>
        You pressed the <strong>n</strong> button and instantiated a click on the <em>Another action</em> button. <a href="#top">Back to top</a>
      </p>
    </div>
    <div id="section3">
      <p>
        You pressed the <strong>w</strong> button and instantiated a click on the <em>We have a link</em> button. <a href="#top">Back to top</a>
      </p>
    </div>
    <div id="section4">
      <p>
        You pressed the <strong>s</strong> button and instantiated a click on the <em>Something else here</em> button. <a href="#top">Back to top</a>
      </p>
    </div>
    <div id="section5">
      <p>
        You pressed the <strong>e</strong> button and instantiated a click on the <em>Separate link</em> button. <a href="#top">Back to top</a>
      </p>
    </div>