Search code examples
javascriptbootstrap-5bootstrap-popover

Bootstrap 5 Popover-Set Content Dynamically from Async Function


I'm trying to load a bootstrap 5 popover with dynamic content from an asynchronous function in which I have to wait for. I'm setting up my popover as so:

document.querySelectorAll('[data-bs-toggle="popover"]').forEach(function (popover) {

    new bootstrap.Popover(popover, {
        content: function () {
            (async () => {

                var x = await SetPopoverContent();
                return x;

            })()
        }
    });   
});

I'm then going back to the database and retrieving my data inside SetPopoverContent():

async function SetPopoverContent(popOver) {
    
    let contentText = '';
    let data = await dotNetReference.invokeMethodAsync('GetChatToInfo', messageId);
    if (data != null && data != undefined) {
        var outerDiv = '<div></div>';
        ...
        
        contentText = outerDiv.innerHTML;
    }
    else {

        contentText = '<p>Loading</p>';
    }
    
    return contentText;
}

I can see my html string inside my popover content function but the content never appears in the popover. Am I doing something wrong with the async callback method?


Solution

  • It won't work with async calls, you need to use .setContent method instead on popover instance (alternatively, you could create popover after async call, or set content to loading... and then use .setContent).

    So, get popover instance, then run your async method, and then assign new content:

    // assign popover instance for later use
    const pop = new bootstrap.Popover(popover);
    
    // or set 'loading' right away
    //const pop = new bootstrap.Popover(popover, {
    //  content: '<p>Loading</p>'
    //});
    
    
    (async() => {
    
        var x = await SetPopoverContent();
    
        // set new content
        pop.setContent({
            '.popover-body': x
        })
    
    })()
    

    demo:

    <!doctype html>
    <html lang="en">
    
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
      <title>Bootstrap Example</title>
      <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
    </head>
    
    <body class="p-3 m-0 border-0 bd-example m-0 border-0">
    
    
    
      <button type="button" class="btn btn-lg btn-danger" data-bs-toggle="popover">Click to toggle popover</button>
    
    
    
      <script>
      
        function dbCall() {
          return new Promise(r => {
            setTimeout(() => r('new data'), 1000);
          })
        }
    
        async function SetPopoverContent() {
    
          let data = await dbCall();
    
          return data;
        }
    
    
        document.querySelectorAll('[data-bs-toggle="popover"]').forEach(function(popover) {
    
          const pop = new bootstrap.Popover(popover, {
            content: 'Loading'
          });
    
          (async() => {
    
            var x = await SetPopoverContent();
    
            pop.setContent({
              '.popover-body': x
            })
          })();
    
        });
      </script>
    
    </body>
    
    </html>

    see example for setting content: Popovers - Methods - setContent example

    The setContent method accepts an object argument, where each property-key is a valid string selector within the popover template, and each related property-value can be string | element | function | null