Search code examples
javascriptevent-propagation

spans inside a link cause js event propagation to fail


We have a top nav bar on the website that we are trying to use spans to create slightly different versions for desktops vs tablets.

Each nav bar item has a full url to handle folks with JS disabled and we use JS to intercept the click and open a flyout menu panel. But if we put spans within the a href link the js intercept stops working and user gets sent to the default link.

Our HTML is below, the first link fail to intercept and goes to the default url, the second link opens the menu as desired.

 <a href="example.com/link1.htm" id="nvbtn1" class="tba"><span class="sf">Full Desktop Title</span><span class="st">Lil Title</span></a>
 <a href="example.com/link2.htm" id="nvbtn2" class="tba">Medium Title</a>

Our JS snippet is below. It is part of a bigger event delegation script, that provides different functions for different links on a page. Note: we don't use JQuery, have simply set $ to function as getElementById.

// overall event delegation
$('mstrwrap').addEventListener('click', function(e) {
  // set eventlisteners for all A HREF links
  if (e.target.nodeName === 'A') {
    // check if is navbar link
    if ((e.target.id) && (e.target.id.includes('nvbtn'))) {
      // disable click through and determine matching nav panel
      e.preventDefault();
      const nvpnl = e.target.id;
      const lk = 'nav' + nvpnl.match(/\d+/g);

      // open nav panel
      openNav(lk, nvpnl);
    }
    // end if navbar link
    // 140 more lines of event delegation
  }
// end set eventListeners
});
// end masterwrap event delegation

The openNav() function checks to see the nav panel is open by checking style.maxHeight and closes it if open, otherwise sets all nav panel to closed value and then open target nav panel.


Solution

  • Add additional condition for span whose parent node is a like below.

    else if (e.target.nodeName === 'SPAN' && e.target.parentNode.nodeName === "A").

    Take new variable as let target = e.target.parentNode;, and replace use it instead of e.target. Try like below.

    // overall event delegation
    document.getElementById('mstrwrap').addEventListener('click', function(e) {
      // set eventlisteners for all A HREF links
      if (e.target.nodeName === 'A') {
        // check if is navbar link
        if ((e.target.id) && (e.target.id.includes('nvbtn'))) {
          // disable click through and determine matching nav panel
          e.preventDefault();
          const nvpnl = e.target.id;
          const lk = 'nav' + nvpnl.match(/\d+/g);
    
          // open nav panel
          openNav(lk, nvpnl);
        }
        // end if navbar link
        // 140 more lines of event delegation
      }
      // add additional condition for span whose parent node is <a>.
      else if (e.target.nodeName === 'SPAN' && e.target.parentNode.nodeName === "A") {
        // set target variable as parent node and replace e.target with this variable
        let target = e.target.parentNode;
        // check if is navbar link
        if ((target.id) && (target.id.includes('nvbtn'))) {
          // disable click through and determine matching nav panel
          e.preventDefault();
          const nvpnl = target.id;
          const lk = 'nav' + nvpnl.match(/\d+/g);
    
          // open nav panel
          openNav(lk, nvpnl);
        }
        // end if navbar link
        // 140 more lines of event delegation
      }
      // end set eventListeners
    });
    // end masterwrap event delegation
    
    function openNav(lk, nvpnl) {
      console.log(lk, nvpnl);
    }
    <div id='mstrwrap'>
      <a href="example.com/link1.htm" id="nvbtn1" class="tba"><span class="sf">Full Desktop Title</span><span class="st">Lil Title</span></a>
      <a href="example.com/link2.htm" id="nvbtn2" class="tba">Medium Title</a>
    </div>


    You can combine if & else if in single if as well. Try improved code as below.

    // overall event delegation
    document.getElementById('mstrwrap').addEventListener('click', function(e) {
      // set eventlisteners for all A HREF links
      if (e.target.nodeName === 'A' || (e.target.nodeName === 'SPAN' && e.target.parentNode.nodeName === "A")) {
        // if target is <a> then use it otherwise use parent node
        let target = e.target.nodeName === 'A' ? e.target : e.target.parentNode;
        
        // NOTE : use target variable instead of e.target
        
        // check if is navbar link
        if ((target.id) && (target.id.includes('nvbtn'))) {
          // disable click through and determine matching nav panel
          e.preventDefault();
          const nvpnl = target.id;
          const lk = 'nav' + nvpnl.match(/\d+/g);
    
          // open nav panel
          openNav(lk, nvpnl);
        }
        // end if navbar link
        // 140 more lines of event delegation
      }
      // end set eventListeners
    });
    // end masterwrap event delegation
    
    function openNav(lk, nvpnl) {
      console.log(lk, nvpnl);
    }
    <div id='mstrwrap'>
      <a href="example.com/link1.htm" id="nvbtn1" class="tba"><span class="sf">Full Desktop Title</span><span class="st">Lil Title</span></a>
      <a href="example.com/link2.htm" id="nvbtn2" class="tba">Medium Title</a>
    </div>