2

I have created a javascript widget that displays popups and anyone can embed it on their websites by pasting a few lines of code before closing </body> tag.

While configuring it a user can specify that he wants a popup to be displayed after 10 secs of page load. But since my script depends on jQuery and before executing its main code it must load it first - sometimes script executes its main() function much later than in 10 secs...

So currently the steps are:

  1. Web page is loaded (I do not know how much time it will take)
  2. My script is loaded (I do not know how much time it will take)
  3. My script loads jQuery if necessary (I do not know how much time it will take)
  4. Only when jQuery is loaded it starts counting 10 secs and then runs displayPopup function

As you can see it's unsafe to run displayPopup function in 10 secs after $(document).ready event because it may not load jQuery or itself yet. And it's okay - it's a technical restriction I can not do anything about (right?).

I just need a way to know how much time has passed since $(document).ready event. And then I will check this number of seconds inside my main() function and decide if I need to display popup immediately (if page was loaded > 10 secs ago) or wait a few seconds.

Script install code:

<script>
(function() {
    var scriptTag = document.createElement("script");
    scriptTag.type = "text/javascript";
    scriptTag.async = true;
    scriptTag.src = "https://www.server.com/script.js";

    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(scriptTag, s);
})();
</script>

Loading jQuery part of script.js

if (window.jQuery === undefined || window.jQuery.fn.jquery !== '1.12.4') {
    var script_tag = document.createElement('script');
    script_tag.setAttribute("type","text/javascript");
    script_tag.setAttribute("src",                             
        "//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"
    );

    if (script_tag.readyState) {
      script_tag.onreadystatechange = function () { 
        if (this.readyState == 'complete' || this.readyState == 'loaded') {
          scriptLoadHandler();            
        }
      };
    } 
    else { 
      script_tag.onload = scriptLoadHandler;
    }

    (document.getElementsByTagName("head")[0] || 
        document.documentElement).appendChild(script_tag);
} 
else {
  jQuery = window.jQuery;
  main();
}

function scriptLoadHandler() {
  jQuery = window.jQuery.noConflict(true);
  main();
}

Main function:

function main() {
  setTimeout(function() {
    displayPopup();
  }, settings['numberOfSeconds'] * 1000);
}
yaru
  • 1,260
  • 1
  • 13
  • 29

4 Answers4

2

In your install code, set a global variable that captures the current time. Since this is right before the <body> ends it should start right when the DOM has loaded. Then in your main() function, check how much time has passed in the variable. If it's more than 10 seconds, you can show your popup immediately. If it's less, you can calculate the time that has passed and set a timeout to fire once 10 seconds has really passed:

<script>
(function() {
  window.myPopupWidgetStartDate = Date.now();

  var scriptTag = document.createElement("script");
  scriptTag.type = "text/javascript";
  scriptTag.async = true;
  scriptTag.src = "https://www.server.com/script.js";

  var s = document.getElementsByTagName('script')[0];
  s.parentNode.insertBefore(scriptTag, s);
})();
</script>


function main() {
  var secondsSinceInstall = Math.round((window.myPopupWidgetStartDate - Date.now());

  if (secondsSinceInstall > settings['numberOfSeconds']) {
    displayPopup();
  } else {
    setTimeout(function() {
      displayPopup();
    }, settings['numberOfSeconds' - secondsSinceInstall] * 1000);
  }
}
skyline3000
  • 7,639
  • 2
  • 24
  • 33
  • Hmmm, maybe I did not express clearly what the problem is... Your code assumes that code of `main()` function is available upon `$(document).ready` callback. But we have 2 problems here: 1) jQuery may not be loaded yet (and call to `document.ready` will fail) and 2) call to `main()` inside `document.ready` assumes that `main()` function body is available, BUT `main()` function is located in my javascript widget file that is loaded from another domain and therefore may not be available upon its call inside `document.ready` – yaru Sep 06 '16 at 15:13
  • Oh, I see - my mistake. In that case, you could start such a timer in your ` – skyline3000 Sep 06 '16 at 15:23
  • I had an idea of modifying my ` – yaru Sep 06 '16 at 15:41
  • Yes, you can definitely achieve it that way. I've edited my answer to capture the time when your install script starts, then you can just compare against that later on in your code similarly to the way you were thinking. – skyline3000 Sep 06 '16 at 16:10
  • it seems that @dimitrisk approach of recording a time inside `document.onreadystatechange` callback is more robust than simply assigning a global variable ` window.myPopupWidgetStartDate` inside ` – yaru Sep 06 '16 at 18:03
  • Well I think it comes down to what you want the user's experience to be. You stated it felt like a really long time before the popup was shown because you were waiting for everything to load first. That's because the browser will still render any HTML & CSS it can, even if loading more scripts at the end of the ``. So if you wait for everything to load, even additional scripts, there will be content rendered on the screen for the user but your timer hasn't started yet. I think it makes more sense to use my approach where the time is based on when content is actually visible to the user. – skyline3000 Sep 06 '16 at 19:19
1

You don't have to mess with timetous or intervals. Its pretty simple actually from my point of view, considering I got everything right.

Once you got document.ready grab the current timestamp. Let it be documentReadyAt.

Once jquery was loaded grab the current timestamp again. Let it be jqReadyAt.

Compare the jqReadyAt against documentReadyAt and find the difference. If jQuery already exists the difference will be insignificant. A matter of couple milliseconds. If jquery had to be loaded will be more than 100ms to few seconds.

Start counting whatever you like.

var documentReadyAt, jqReadyAt;
document.onreadystatechange = function () {
    if (document.readyState === "complete" && documentReadyAt === undefined) {
        documentReadyAt = new Date();
        loadJQ(); // Loading jQuery part of script.js. 
    }
}

//function loadJQ(){ ...whatever you wrote to load jquery }

//For the sake of handling same logic in one place will assign a variable to jqReadyAt here. Could also be done at `main()` without noticeable cost.
function scriptLoadHandler() {
  jqReadyAt = new Date();
  jQuery = window.jQuery.noConflict(true);
  main();
}

//Main will change to that one.
function main() {
  var millisPassedSinceDocLoaded = jqReadyAt.getTime() - documentReadyAt.getTime();
  setTimeout(function() {
    displayPopup();
  }, (settings['numberOfSeconds'] * 1000) - millisPassedSinceDocLoaded );
}
dimitrisk
  • 774
  • 9
  • 15
  • Will `document.onreadystatechange` work with all modern browsers? In proposed solution above by @skyline3000 `window.myPopupWidgetStartDate = Date.now();` will work for sure (I think), but will these 2 approaches (yours and skyline3000) return the same time (differ only by a few milliseconds)? Your approach is based on listening for `document.onreadystatechange` event seems more clear, than brutally assigning a date to `window. myPopupWidgetStartDate` global variable (because at a time of its assignment document is not in `ready` state yet, but it will in a few milliseconds (or will it not?).. – yaru Sep 06 '16 at 17:16
  • If you plan to provide support for <=IE 6. As of Chrome is supported since version 1.0. FF since version 1.7 Functional wise there are differences between these approaches. The `onreadystatechange` gets `readyState = complete` only once ALL content and scripts/css got loaded. That is any jQ script as well in case the page has a reference on it. In case you load jQ yourself you 'll count only the time passed since all content loaded. The other approach does not ensure that. If more scripts follow below the time returned wont be the time you are looking for document.ready. – dimitrisk Sep 06 '16 at 17:31
1

you would need to use performance.timing API. there are many useful data there to use, like domContentLoadedEventEnd, domContentLoadedEventStart, loadEventStart, loadEventEnd etc. for your case to measure how much time passed since the dom ready event is triggered, in your main function use below code to get the passed time. once get the elapsed you can now decide how to proceed.

var timePassedSinceDomReady = new Date() - new Date(performance.timing.domContentLoadedEventEnd);
console.log('total time passed: ' + timePassedSinceDomReady + 'ms');

most modern browsers support it. for more details on timing API a see https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming, http://www.html5rocks.com/en/tutorials/webperformance/basics/

Sufian Saory
  • 862
  • 9
  • 14
  • That's very elegant solution indeed! It seems support on that goes down to IE9 but won't support Safari or Android. That sucks cause the implementation rocks! – dimitrisk Sep 06 '16 at 17:41
  • Android and Safari do support. here is compatibility table http://caniuse.com/#search=performance.timing – Sufian Saory Sep 07 '16 at 08:16
-1

You could try setting a flag for when jQuery is loaded, then start the setTimeout straight from the document.ready, and check if jQuery is loaded before displaying the popup.

Otherwise you might want to take a look at this question.

Community
  • 1
  • 1
KoreanwGlasses
  • 284
  • 1
  • 7