Search code examples
bootstrap-5popoverpopper.jsbootstrap-popover

Bootstrap popover doesn't pop up


I do the simple render users from jsonplaceholder with XMLHttpRequest. And I wanna do the popover for every user. This popover will show the username, email, phone, website. I don't know if this can be called error. Because the console does not display any errors and the pop-up window sometimes works, but most often it does not. No visible errors. I do everything according to bootstrap instructions. I'll post the code below.

My AJAX.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65"
      crossorigin="anonymous"
    />
    <title>AJAX</title>
  </head>
  <body>
    <h1>AJAX</h1>
    <button class="btn button btn-primary get-users-btn">Get Users</button>
    <div class="container"></div>
    <script src="AJAX.js"></script>
    <script src="renderUsers.js"></script>
    <script
      src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js"
      integrity="sha384-oBqDVmMz9ATKxIep9tiCxS/Z9fNfEXiDAYTujMAeBAsjFuCZSmKbSSUnQlmh/jp3"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js"
      integrity="sha384-cuYeSxntonz0PPNlHhBs68uyIAVpIIOZZ5JqeqvYYIcEL727kskC66kF92t6Xl2V"
      crossorigin="anonymous"
    ></script>
    <script>
      const popoverTriggerList = document.querySelectorAll(
        '[data-bs-toggle="popover"]'
      );
      const popoverList = [...popoverTriggerList].map(
        (popoverTriggerEl) => new bootstrap.Popover(popoverTriggerEl)
      );
    </script>
  </body>
</html>

My AJAX.js:

const btn = document.querySelector(".get-users-btn");
const container = document.querySelector(".container");

function getUsers(callback) {
  const request = new XMLHttpRequest();
  request.open("GET", "https://jsonplaceholder.typicode.com/users");
  request.addEventListener("load", () => {
    const response = JSON.parse(request.responseText);
    callback(response);
  });
  request.addEventListener("error", () => {
    console.log("error");
  });
  request.send();
}

getUsers((response) => {
  console.log(response);
});

My renderUsers.js:

function renderUsers(response) {
  const fragment = document.createDocumentFragment();
  response.forEach((user) => {
    const card = document.createElement("div");
    card.classList.add("card");
    const cardBody = document.createElement("div");
    cardBody.classList.add("card-body");

    const userBtn = document.createElement("button");
    userBtn.setAttribute("type", "button");
    userBtn.classList.add("btn", "btn-info", "btn-lg");
    userBtn.setAttribute("data-bs-toggle", "popover");
    userBtn.setAttribute(
      "data-bs-title",
      "Дополнительная информация про человека:"
    );
    userBtn.setAttribute("data-bs-content", `${user.username}`);

    const title = document.createElement("h5");
    title.classList.add("card-title");
    title.textContent = user.name;

    userBtn.appendChild(title);
    cardBody.appendChild(userBtn);
    card.appendChild(cardBody);
    fragment.appendChild(card);
  });
  container.appendChild(fragment);
}

btn.addEventListener("click", (e) => {
  getUsers(renderUsers);
});

enter image description here I changed

const userBtn = document.createElement("a")

to

const userBtn = document.createElement("button"); 

I use Live Server. It doesn't work when you click on the name, but sometimes I find that it works, after reboot it doesn't work again. I don’t want to ask stupid questions, but this is just some kind of magic for me. Thank's


Solution

  • As AJAX is asynchronous, it's best to initiate popover elements after they're added to the DOM, because when BS code runs, they might or might not be on the DOM, and then you get the described behaviour. For example, move the existing code into a function and then call it when list is appended:

      container.appendChild(fragment);
      // initiate popovers now
      initPopovers();
    
    // find all popovers and initiate
    function initPopovers() {
    
      const popoverTriggerList = document.querySelectorAll(
            '[data-bs-toggle="popover"]'
          );
          const popoverList = [...popoverTriggerList].map(
            (popoverTriggerEl) => new bootstrap.Popover(popoverTriggerEl)
          );     
            
    }
    

    demo:

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous" />
      <title>AJAX</title>
    </head>
    
    <body>
      <h1>AJAX</h1>
      <button class="btn button btn-primary get-users-btn">Get Users</button>
      <div class="container"></div>
      <script>
        const btn = document.querySelector('.get-users-btn');
        const container = document.querySelector('.container');
    
        function getUsers(callback) {
          const request = new XMLHttpRequest();
          request.open('GET', 'https://jsonplaceholder.typicode.com/users');
          request.addEventListener('load', () => {
            const response = JSON.parse(request.responseText);
            callback(response);
          });
          request.addEventListener('error', () => {
            console.log('error');
          });
          request.send();
        }
    
        getUsers((response) => {
          console.log(response);
        });
    
        function renderUsers(response) {
          const fragment = document.createDocumentFragment();
          response.forEach((user) => {
            const card = document.createElement('div');
            card.classList.add('card');
            const cardBody = document.createElement('div');
            cardBody.classList.add('card-body');
    
            const userBtn = document.createElement('a');
            userBtn.setAttribute('type', 'button');
            userBtn.classList.add('btn', 'btn-info', 'btn-lg');
            userBtn.setAttribute('data-bs-toggle', 'popover');
            userBtn.setAttribute(
              'data-bs-title',
              'Дополнительная информация про человека:'
            );
            userBtn.setAttribute('data-bs-content', `${user.username}`);
    
            const title = document.createElement('h5');
            title.classList.add('card-title');
            title.textContent = user.name;
    
            userBtn.appendChild(title);
            cardBody.appendChild(userBtn);
            card.appendChild(cardBody);
            fragment.appendChild(card);
          });
          container.appendChild(fragment);
    
          initPopovers();
        }
    
        btn.addEventListener('click', (e) => {
          getUsers(renderUsers);
        });
      </script>
      <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js" integrity="sha384-oBqDVmMz9ATKxIep9tiCxS/Z9fNfEXiDAYTujMAeBAsjFuCZSmKbSSUnQlmh/jp3" crossorigin="anonymous"></script>
      <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js" integrity="sha384-cuYeSxntonz0PPNlHhBs68uyIAVpIIOZZ5JqeqvYYIcEL727kskC66kF92t6Xl2V" crossorigin="anonymous"></script>
      <script>
        function initPopovers() {
          const popoverTriggerList = document.querySelectorAll(
            '[data-bs-toggle="popover"]'
          );
          const popoverList = [...popoverTriggerList].map(
            (popoverTriggerEl) => new bootstrap.Popover(popoverTriggerEl)
          );
        }
      </script>
    </body>
    
    </html>