5

I am testing with pure JavaScript if browser seems to support HTML5 and if so, I want to load jQuery and then process the rest of page. If not, some redirection will occur.

  <script type="text/javascript">
    var canvas = document.createElement('canvas');
    if (canvas && canvas.getContext && canvas.getContext('2d')) {
      var s = document.getElementsByTagName('script')[0];
      var jq = document.createElement('script');
      jq.type = 'text/javascript';
      jq.src = 'js/jquery.js';
      s.parentNode.insertBefore(jq, s);
    }
    else {
      // ... redirection ...
    }
  </script>
  <script type="text/javascript">
    $(function () {
      //...
    }
  </script>

But the code above is not working properly, because I got error

  Uncaught ReferenceError: $ is not defined

which is clearly saying that jQuery library has not been loaded.

Why? What is wrong with conditional script loading in my code above?

Ωmega
  • 37,727
  • 29
  • 115
  • 183
  • 1
    I think it is because at parse-time you don't have that variable `jQuery $` since you are loading it after the script. you have to add your script dynamically then inside the node too before you insert it then I think it will work fine .. – CME64 Jul 04 '13 at 03:09
  • Have you tried to View Source to see if jquery is loaded or properly inside the script element? Could you put your code in jsfiddle? – Edper Jul 04 '13 at 03:12
  • @KevinNacios - Wrong. – Ωmega Jul 04 '13 at 03:13
  • @Edper - Sorry, I can't publish my entire code. jQuery is loaded, but most likely not at right time... – Ωmega Jul 04 '13 at 03:14
  • @Ωmega ok I understand. CME64 is in the right direction I believe. – Edper Jul 04 '13 at 03:17
  • Why not always load jquery? – zerkms Jul 04 '13 at 03:24
  • @zerkms - There are more scripts to load, jQ is just one of them. – Ωmega Jul 04 '13 at 03:27
  • I managed to insert the code and run it so far but still, it is not loading in order, I have to spend some time on it, hang on .. – CME64 Jul 04 '13 at 04:03
  • I loved this question .. keep the fun work on, and check my answer – CME64 Jul 04 '13 at 04:30
  • Why are you hosting your own jquery? Why not use an established CDN and bank off the fact that jquery will be cached? – Alan Jul 04 '13 at 05:01

4 Answers4

4

This is a case where it may make sense to use document.write(). You'd need to put this code in the <body> instead of the <head>:

  <script type="text/javascript">
    var canvas = document.createElement('canvas');
    if (canvas && canvas.getContext && canvas.getContext('2d')) {
      document.write( '<script src="js/jquery.js"><\/script>' );
    }
    else {
      // ... redirection ...
    }
  </script>
  <script type="text/javascript">
    $(function () {
      //...
    }
  </script>

Or, you may be able to use an ordinary <script> tag to load jQuery, but put it after your conditional redirection:

  <script>
    var canvas = document.createElement('canvas');
    if( !( canvas && canvas.getContext && canvas.getContext('2d') ) ) {
      // ... redirection ...
    }
  </script>
  <script src="js/jquery.js"></script>
  <script>
    $(function () {
      //...
    }
  </script>

With either of these approaches, the order of execution is:

  1. The first <script>.
  2. The loading of jquery.js, whether done with document.write() or a simple <script> tag.
  3. The final script.
Michael Geary
  • 26,814
  • 8
  • 56
  • 71
  • Redirection was just an example. It I more complex. I need to force browser to wait for library load before process the other scripts - that is my issue here. – Ωmega Jul 04 '13 at 03:18
  • 1
    If you use `document.write()` it is synchronous. Your other scripts will not execute until after that. – Michael Geary Jul 04 '13 at 03:19
  • 3
    http://stackoverflow.com/questions/802854/why-is-document-write-considered-a-bad-practice – Alan Jul 04 '13 at 03:45
  • @Alan - Yes, I'm familiar with the reasons not to use `document.write()`. None of them apply here; in fact, at least one of those reasons "not" to use it is exactly the reason why it's a good solution here: the fact that it is synchronous and delays the execution of other scripts that follow it. – Michael Geary Jul 04 '13 at 03:56
  • @Alan this is one of the rare cases where `document.write` _can't_ be avoided unless you make the rest of the script wait for jQuery to load (which isn't a bad idea)(\*cough\* `require.js`). And yes, this approach can't be used to load jQuery synchronously after the page load. – John Dvorak Jul 04 '13 at 04:30
  • 1
    @MichaelGeary the "doesn't work with xhtml" reason _does_ apply here. Though, I would say, XHTML is dead. – John Dvorak Jul 04 '13 at 04:33
  • @JanDvorak - Yes, that's a good point if one is using XHTML. But since the OP said "I am testing with pure JavaScript if browser seems to support HTML5..." it's probably a safe bet that XHTML is not a factor here. – Michael Geary Jul 04 '13 at 04:46
3

When you insert a script tag like you are, it will be loaded in the background, not immediately and thus your next script will run before jQuery is loaded. You will need to attach a listener such that you know when jQuery is successfully loaded and you can then run your scripts that use jQuery.

Here's an article that describes how to know when a dynamically loaded script is loaded: http://software.intel.com/en-us/blogs/2010/05/22/dynamically-load-javascript-with-load-completion-notification.


FYI, in your specific case, you also could just have a static script tag that loads jQuery, but place your script that detects whether to redirect or not BEFORE the jQuery script tag. That would be the simplest option.

<script type="text/javascript">
    var canvas = document.createElement('canvas');
    if (!canvas || !canvas.getContext || !canvas.getContext('2d')) {
        // redirect here or whatever
    }
</script>
<script type="text/javascript" src="jquery.js">
</script>
<script type="text/javascript">
    $(function () {
      //...
    }
</script>
jfriend00
  • 580,699
  • 78
  • 809
  • 825
  • Any chance to add "waiting script" after the dynamic script loading, which would prevent the other scripts to execute? I should not modify existing page that is beyond the check point... – Ωmega Jul 04 '13 at 03:20
  • @Ωmega - no, you can't make it wait to load the script when loading the script the way you are. I did add another pretty simple option to my answer though. – jfriend00 Jul 04 '13 at 03:21
  • As I already wrote in the other comment (see answer from Michael Geary), redirection was just an example, it is more complex. – Ωmega Jul 04 '13 at 03:21
  • @Ωmega - then the first part of my answer is the way to do it. You run the rest of your initialization code in a callback function that is called when jQuery is loaded. – jfriend00 Jul 04 '13 at 03:23
  • I was hoping to find solution which would work without modification of the rest of page content (html, scripts,...) – Ωmega Jul 04 '13 at 03:26
  • @Ωmega - How about the option proposed in the last paragraph of my answer? Just always load jQuery either way via normal script tag and only use it if you want. That lets the rest of your code work normally. – jfriend00 Jul 04 '13 at 03:30
  • Why the downvote? Doesn't this answer provide a number of options available to the OP? – jfriend00 Jul 04 '13 at 04:10
  • @jfriend00 you can make a script that loads synchronously. Just use `document.write` – John Dvorak Jul 04 '13 at 04:25
  • @JanDvorak - sure and there are other answers that offer that. That has it's own issues and is only appropriate in some cases. For example, it can't be used in the head tag which it sounds like may be the case here in order to be compatible with everything else in the web page. In any case, if you like that option, just vote for the answer that includes that. You don't have to downvote this one. – jfriend00 Jul 04 '13 at 04:46
  • @jfriend00 I didn't downvote. In fact, I'm considering an upvote, but I don't know if it will really prevent loading jQuery. Do you have any resource backing that claim? Concerning DW - I did upvote that answer. – John Dvorak Jul 04 '13 at 04:52
  • @JanDvorak - OK thx. My first option described how to detect when a dynamically loaded jQuery has finished loading. The OP said they didn't want that complication so I suggested that the simplest option (which the OP seems to want) is to let it load and only use it if they want to. If they're redirecting, I'm not sure why this is a problem, particularly if jQuery is loaded from a CDN (and usually in the browser cache). – jfriend00 Jul 04 '13 at 05:17
1

finally working like a charm, I'm relieved myself !

<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>JS Bin</title>
   <script type="text/javascript">
      window.onload = function(){
        var jqu = "$(console.log('worked'));";
        var canvas = document.createElement('canvas');
        if (canvas && canvas.getContext && canvas.getContext('2d')) {
          var s = document.getElementsByTagName('head')[0];
          var jq = document.createElement('script');
          jq.setAttribute('type','text/javascript');
          jq.innerHTML = jqu;
          var jqLoad = document.createElement('script');
          jqLoad.setAttribute('type','text/javascript');
          jqLoad.setAttribute('src','jquery-1.10.0.js');
          jqLoad.setAttribute('id','jqloader');
          s.appendChild(jqLoad);
          document.getElementById('jqloader').onload = function(){
            console.log('loaded');
            s.appendChild(jq);
          }
        }
        else {
        // ... redirection ...
        }
        console.log(document);
      }
  </script>
</head>
<body>

</body>
</html>

jsbin Demo

explanation :

1- using dom functions to append or insert elements are always the best (dynamic and safer more than anything else), and document.write is not recommended over that.

2- at parse-time, whatever functions you have in your script will be evaluated thus you will get an error if you have the script and not loaded the library yet.

3- loading the library and executing the relevant script in the same tag is not recommended. better do the script in another tag (after loading is done completely) to ensure it will work.

4- events for document.onload ensures that the document is loaded and the doms exist so you can append children to them. as for the document.getElementById('jqloader').onload it was just to insure that the jquery library is loaded completely and added to the document, and only then the script will be added after and evaluated.

CME64
  • 1,643
  • 12
  • 24
0

As others have said, the reason you're getting an error is because you've loaded jQuery asynchronously and it hasn't loaded yet.

There are two ways to accomplish what you want.

You can poll for window.jQuery, or you can use an asynchronous loader callback.

Since you only load jQuery only when you detect canvas support, you won't have to worry about supporting old browsers.

var async_script_load = function (s, callback) {
    var script;

    script = document.createElement("script");
    script.async = "async";

    if (s.scriptCharset) {
        script.charset = s.scriptCharset;
    }

    script.src = s.url;

    // Attach handlers for all browsers
    script.onload = script.onreadystatechange = function () {
        if (!script.readyState || /loaded|complete/.test(script.readyState)) {
            // Handle memory leak in IE
            script.onload = script.onreadystatechange = null;

            // Remove the script
            if (head && script.parentNode) {
                head.removeChild(script);
            }

            // Dereference the script
            script = undefined;
            callback(200, "success");
        }
    };
    // Use insertBefore instead of appendChild  to circumvent an IE6 bug.
    // This arises when a base node is used (#2709 and #4378).
    head.insertBefore(script, head.firstChild);
};

async_loader({url:'http://tempuri.org/jquery.min.js'},function() {
   //call jquery here.
});

For a polling method, it's as simple as:

var checkJq = function() { 
    if(window.jQuery) {
      //do jQuery
    } else {
       setTimeout(checkJq,100);
    }
}

setTimeout(checkJq,100);
Alan
  • 42,055
  • 16
  • 107
  • 130