Search code examples
javascriptbootstrap-popover

Manual options to hide a Bootstrap 5 popover


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/bootstrap@5.3.3/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/core@2.11.8/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/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>


Solution

  • 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/bootstrap@5.3.3/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/core@2.11.8/dist/umd/popper.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/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>