Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

I have multiple iframes, how to detect the first one that loads

I'm creating iframes dynamically to display documents obtained via a url. Each document is in an iframe which is, in turn, in a div. I have a tab bar that allows any document to be displayed while hiding the rest, a typical tabbed page.

I would like to display the div containing the iframe that receives the first response, i.e. display the first that loaded and hide the rest. There is no way to predict which will load first so I need a way to detect the first and display it and hide all of the rest.

I think I can do this by having the iframe onload function check a global boolean that is set to true when the first onload handler runs.

I'm not sure why, but this feels error prone. Is there a better way to do this?

var firstDocumentReceived = false ; 
function buildFileTabs(evt) {
    firstDocumentReceived = false ;
    var files = evt.target.files;       // files is a list of File objects. 

    for (var i = 0, f; f = files[i]; i++) {
        addTab ( files[i].name );
    }

// create a div to display the requested document
function addTab ( fileName ) {
    var newDiv = document.createElement("div");
    newDiv.id = fileName.replace(".","_") + newRandomNumber() ; 

    // create an iframe to place in the div
    var newIframe = document.createElement("iframe");
    newIframe.onload=iframeLoaded ;
    newIframe.setAttribute("seamless", true );
    newIframe.setAttribute("div_id" , newDiv.id) ;
    newIframe.src = url ; // the iframe will contain a web page returned by the FDS server

    // nest the iframe in the div element
    newDiv.appendChild(newIframe) ;

    // nest the div element in the parent div
    document.getElementById("documentDisplay").appendChild(newDiv) ;
    ...

function iframeLoaded ( event ) {
    if ( firstDocumentReceived === false ) {
        firstDocumentReceived = true ;
        // show the div associated with this event
    } else {
        // hide the div associated with this event
    }
like image 438
user903724 Avatar asked Nov 09 '22 15:11

user903724


1 Answers

Below, I describe two alternative ways (A & B) you can resolve the first iframe that is loaded:

A) Stop loading the other iframes when the first iframe is loaded.

You will need to set up a way to track the new iframes that are being added, such as the newIframes array shown below. You can do this by tracking the newIframes or by retrieving them from the DOM before the below logic is run in your iframeLoaded() function.

Below is an example on how you can modify your iframeLoaded() function by looping through your other iframes, stop loading them, and hide them:

function iframeLoaded(evt) {
  newIframes.forEach(function (newIframe) {
    if (newIframe.getAttribute('div_id') !== evt.target.getAttribute('div_id')) {
      // stop loading and hide the other iframes
      if (navigator.appName === 'Microsoft Internet Explorer') {
        newIframe.document.execCommand('Stop');
      } else {
        newIframe.stop();
      }
      newIframe.setAttribute('hidden', 'true'); // you can also delete it or set the css display to none
    }
  });
}

B) Use Promise.race() as suggested by joews.

Promises can be created using jQuery, but ES6 harmony includes a Promise.race() method for the Promise object. Basically, the first promise to finish is resolved in a race between promises.

Below is an example for how you can modify your code to use this ES6 feature:

1) Create a new function createIframePromise() that creates a promise for an iframe.

function createIframePromise(newIframe) {
    var iframePromise = new Promise(function (resolve, reject) {
        newIframe.onload = function (evt) {
            // when the iframe finish loading, 
            // resolve with the iframe's identifier.
            resolve(evt.target.getAttribute('div_id'));
        }
    });
    return iframePromise;
}

2) Create an empty array in the outer scope to hold the different iframe promises.

// hold the iframe promises.
var iframePromises = []; 

3) Modify your addTab() function by creating an iframe promise and then pushing it to the iframePromises array.

Make sure to also remove the line newIframe.onload=iframeLoaded; from this function because we moved it to the newly created createIframePromise() function in #1 above.

function addTab(fileName) {
    // after "newIframe" with properties is created,
    // create its promise and push it to the array.
    iframePromises.push(createIframePromise(newIframe));
    // ...
}

4) Modify your buildFileTabs() function to set up the race after the iframes are created and their promises are stored in the iframePromises array.

function buildFileTabs(evt) {
    // ...
    Promise.race(iframePromises).then(function (firstIframeId) {
        // resolve...
        // put logic to show the first loaded iframe here. 
        // (all other iframe promises lose the race)
    }, function () {
        // reject...
        // nothing b/c "createIframePromise()" doesn't reject anything.
    });
}
like image 79
boombox Avatar answered Nov 14 '22 23:11

boombox