skip to Main Content

My use-case is this: I have a list of files that users can click on to download. I’d like them to be able to hover over a file and see the file size and checksum. I also want them to be able to select and copy the checksum value to their clipboard if so desired. Initializing the popover on each item and manually getting it to show on mouseover works fine. However, I’m not sure how to hide the popover. I was hoping I could add a mouseleave eventListener to the popover itself, but that doesn’t seem to be an option. Obviously, I want to reserve clicking of the element itself for initiating the download.

<!doctype html>
<html>

<head>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.0/css/all.min.css" integrity="sha512-9xKTRVabjVeZmc+GUW8GgSmcREDunMM+Dt/GrzchfN8tkwHizc5RP4Ok/MXFFy5rIjJjzhndFScTceq5e6GvVQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />

  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
  <script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-0pUGZvbkm6XF6gxjEnlmuGrJXVbNuzT9qBBavbLwCsOGabYfZo0T0to5eqruptLy" crossorigin="anonymous"></script>
</head>

<body>
  <br/>
  <a href="#"
   data-bs-toggle="popover"
   data-bs-content="size: 5MB<br/>sha1: 123456789"
   >
   FILE_TO_DOWNLOAD_1
 </a>
  <br/>
  <br/>
  <a href="#"
   data-bs-toggle="popover"
   data-bs-content="size: 5MB<br/>sha1: 123456789"
>
    FILE_TO_DOWNLOAD_2
</a>

  <script>
    $(document).ready(function() {

      var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
      var popoverList = popoverTriggerList.map(function(popoverTriggerEl) {
        var popover = new bootstrap.Popover(popoverTriggerEl, {
          container: 'body',
          html: true,
          trigger: 'manual'
        });
        popoverTriggerEl.addEventListener('mouseover', (event) => {
          popover.show();
        });
        return popover;
      });

    });
  </script>

</body>

2

Answers


  1. Idea is to allow for a few ms delay before hiding the popover. This grace period is cancel the hiding should the element or the popover got hovered.

    Technically, each popover’s tip property gives a handle to the popover element, so you can addEventListener to it.

    For a nicer touch, we close all other popovers when opening a new one.

    var elements = document.querySelectorAll('[data-bs-toggle="popover"]')
    var popoverTriggerList = [].slice.call(elements);
    
    var popoverList = popoverTriggerList.map(function(popoverTriggerEl) {
    
      var popover = new bootstrap.Popover(popoverTriggerEl, {
        container: 'body',
        html: true,
        trigger: 'manual'
      });
    
      var tip = null;
      var timeout = null
    
      function showPopover() {
        clearTimeout(timeout);
        if (!tip) {
          popover.show();
    
          popoverList.forEach(function(other) {
            if (other !== popover) {
              other.hide()
            }
          })
    
          tip = popover.tip;
          tip.addEventListener('mouseenter', (ev) => {
            showPopover();
          });
          tip.addEventListener('mouseleave', (ev) => {
            hidePopover();
          });
        }
      }
    
      function hidePopover() {
        timeout = setTimeout(function() {
          popover.hide();
          tip.remove(); // remove event handlers?
          timeout = null;
          tip = null;
        }, 250)
      }
    
      popoverTriggerEl.addEventListener('mouseenter', (ev) => {
        showPopover()
      })
    
      popoverTriggerEl.addEventListener('mouseleave', (ev) => {
        hidePopover()
      });
    
      return popover;
    });
    <!doctype html>
    <html>
    
    <head>
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
      <script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js"></script>
    </head>
    
    <body style="background: #333; color: #fff; padding: 20px;">
      <br />
      <a href="#" data-bs-toggle="popover" data-bs-content="size: 5MB<br/>sha1: 123456789">
            FILE_TO_DOWNLOAD_1
        </a>
      <br />
      <br />
      <a href="#" data-bs-toggle="popover" data-bs-content="size: 5MB<br/>sha1: 123456789">
            FILE_TO_DOWNLOAD_2
        </a>
    
    </body>
    Login or Signup to reply.
  2. I implemented the following solution:

    Just like you, I use a mouseover event to show the popover. However, I also store a reference to the element to avoid repeating the event.

    After that, I add a class to the element to identify it using jQuery’s is function (equivalent to matches in vanilla JavaScript).

    Finally, I add an event to the element that monitors all mouseover and mouseout events. If any of these events indicate that the mouse is no longer over the clicked button or the popover itself, I close the popover.

    I added debounce because when you move the mouse between the clicked element and the popover, there’s a small gap between them. Without debounce, which adds a slight delay, the hide event would trigger immediately.

    In simpler and more summarized terms: When you click the button, the popover is activated and only disappears from the screen when the user moves the mouse away from both elements.

    $(document).ready(function() {
      var increment = 0
      var $html = $("html")
    
      var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
      var popoverList = popoverTriggerList.map(function(popoverTriggerEl) {
        var popover = new bootstrap.Popover(popoverTriggerEl, {
          container: 'body',
          html: true,
          trigger: 'manual'
        });
        popoverTriggerEl.addEventListener('mouseover', (event) => {
          var $thisElement = $(event.target)
          if (($thisElement.attr("class") || "").includes("keep-popover-")) { // no repeat event
            return
          }
    
          popover.show();
          var popoverId = $(popover.tip).attr("id")
    
          var className = `keep-popover-${++increment}`
          $thisElement.addClass(className)
          const debouncedClosePopover = debounce(closePopover, 250)
    
          $html.on("mouseover mouseout", debouncedClosePopover)
    
          function closePopover(event) {
            var $target = $(event.target),
              keepPopover = $target.is(`.${className}, .${className} *, #${popoverId}, #${popoverId} *`)
    
            if (!keepPopover) {
              popover.hide()
              $thisElement.removeClass(className)
              $html.off("mouseover mouseout", debouncedClosePopover)
            }
          }
        });
        return popover;
      });
    
    });
    
    function debounce(fn, delay) {
      let mainIndex = 0
    
      return (...args) => {
        mainIndex++
        const currentIndex = mainIndex
        setTimeout(() => {
          if (currentIndex === mainIndex) {
            fn(...args)
          }
        }, delay)
      }
    }
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.0/css/all.min.css" rel="stylesheet"/>
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"/>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js"></script>
    <a href="#"
       data-bs-toggle="popover"
       data-bs-content="size: 5MB<br/>sha1: 123456789"
       >
       FILE_TO_DOWNLOAD_1
     </a>
      <br/>
      <br/>
      <a href="#"
       data-bs-toggle="popover"
       data-bs-content="size: 5MB<br/>sha1: 123456789"
    >
        FILE_TO_DOWNLOAD_2
    </a>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search