17

on a Click, the following gets loaded:

$.getScript('/javascripts/contacts.js');

How can I write a statement that says

if the getScript above is already loaded do X,, if not loaded it, do Y...?

Thanks

AnApprentice
  • 108,152
  • 195
  • 629
  • 1,012
  • Why do you want to do this? Are you talking about caching? or are you talking about conditional evaluation? – geocar Apr 30 '11 at 23:14
  • thanks for asking. I want to do this as my app has several modules so to speak.. Contacts, Tasks etc... I don't want to load in all the JS unless needed. So if the user clicks Contacts, I then want to load the contacts.js file, but if they click out and then click back to contacts. I don't want to RELOAD it. ? – AnApprentice Apr 30 '11 at 23:15
  • 3
    Your browser will already do this because of caching. – geocar Apr 30 '11 at 23:27

8 Answers8

15
var gotContacts = 0;
function getContacts(){
    if (!gotContacts){
        $.getScript('/javascripts/contacts.js', function() {
            gotContacts = 1;
            doSomethingWithContacts();
        });
    }else{
        doSomethingWithContacts();
    }
}
Mauvis Ledford
  • 40,827
  • 17
  • 81
  • 86
  • 3
    If there is a danger of multiple requests occurring in the time it takes the script to be fetched then you can also add a pending flag ('gettingContacts') before getScript so that only the first request is fired. If you don't do this you can end up with the script being loaded into the DOM multiple times. – notreadbyhumans Apr 06 '14 at 12:05
7

My situation was different. I used a menu to load content into a div using jquery. To prevent too many scripts from being present in the header section, I used .getscript to load the file only if that link was called. Here is the solution I came up with to prevent duplicates:

First I create a function that was responsible for loading the script. This is part of the global.js file on my site and the global.js file is hard coded into the header:

var loadedScripts=new Array();

function loadScriptFiles(scriptArray){

    if($.isArray(scriptArray)){
        $.each(scriptArray, function(intIndex, objValue){
            if($.inArray(objValue, loadedScripts) < 0){
                $.getScript(objValue, function(){
                    loadedScripts.push(objValue);
                });
            }
        });
    }
    else{
            if($.inArray(script, loadedScripts) < 0){
                $.getScript(script, function(){
                    loadedScripts.push(objValue);
                });
            }





    }
}

This takes two types of loads, either an array, or a single script value. If it's an array, it loops through each element and loads it. If it's not an array, then it'll just load one file. You'll see that part in a minute.

Important parts: See I've created an array in the global scope (outside of the function) called loadedScripts. Because this is part of the global.js file, it never gets reloaded and can be called from anywhere. But for our purposes, I simply check to see if the script name being loaded is already in the array using:

if($.inArray(objValue, loadedScripts) < 0){

Because the jquery inarray function returns -1 if the value is not found, I only continue if the value is less than 0. Anything else means the value is in the array.

Here is how you call the function to be used. Place this code anywhere (I put it at the top) of the dynamic page that will be called into play.

<script type="text/javascript">
    var scriptArray=new Array();
    scriptArray[0]="[REPLACE THIS WITH THE PATH TO THE JS FILE]";
    scriptArray[1]="[REPLACE THIS WITH THE PATH TO THE JS FILE]";
    loadScriptFiles(scriptArray);
</script>

If done properly, you'll notice it only gets loaded once.

Bellash
  • 7,560
  • 6
  • 53
  • 86
Eddie
  • 71
  • 1
6

I've created a plugin that does almost exactly what you want: $.getScriptOnce().

If you try to load the same script twice or more it will not load the script and return false.

But basically, it defines this alternative function of $.getScript()

(function($) {
    $.getScriptOnce = function(url, successhandler) {
       if ($.getScriptOnce.loaded.indexOf(url) === -1) {
            $.getScriptOnce.loaded.push(url); 
            if (successhandler === undefined) {
                return $.getScript(url);
            } else {
                return $.getScript(url, function(script, textStatus, jqXHR) {
                    successhandler(script, textStatus, jqXHR);
                });
            }
       } else {
           return false;
       }

    };

    $.getScriptOnce.loaded = [];

}(jQuery));

And then simply call $.getScriptOnce('yourFile.js')

João Pimentel Ferreira
  • 14,289
  • 10
  • 80
  • 109
Loran
  • 111
  • 3
  • 5
1
 $.getScript( "https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js")
         .done(function () {
           alert('is called');
        })
       `.fail(function () {
                   alert('is not called');
            });
Labb Avdiu
  • 211
  • 3
  • 3
  • While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value. – Nic3500 Sep 21 '18 at 12:57
1

Use require.js

Gabriele Petrioli
  • 191,379
  • 34
  • 261
  • 317
1

Since $.getScript() return promise interface, you can do something like this:

$.getScriptOnce = (function() {
  var cache = {};
  return function(url, success) {
    if (!cache[url]) {
      cache[url] = $.getScript(url, success);
    } else if($.isFunction(success)) {
      cache[url].done(success);
    }
    return cache[url];
  }
})();

// Script will be loaded and executed once.
// All callbacks will be executed on success.

$.getScriptOnce('hello.js', function(data), {
   alert('callback 1');
});

$.getScriptOnce('hello.js', function(data), {
   alert('callback 2');
});

$.getScriptOnce('hello.js');

$.getScriptOnce('hello.js').done(function() {
  alert('loaded');
}).error(function() {
  alert('error');
});

hello.js

alert('Hello from hello.js');
NoSkill
  • 718
  • 5
  • 15
0

I also wrote such function. You can test with the Network tab in Firebug or Chrome dev tools. This just loads the same file once.

Pay attention not to download the second time immediately after the previous, because the function just assumes the file was loaded, after the callback is called, i.e., after the file was loaded successfully.

var getScriptOnce = (function(url, callback) {
  var scriptArray = []; //array of urls
  return function (url, callback) {
      //the global array doesn't have such url
      if (scriptArray.indexOf(url) === -1){
          if (typeof callback === 'function') {
              return $.getScript(url, function(script, textStatus, jqXHR) {
                  scriptArray.push(url);
                  callback(script, textStatus, jqXHR);
              });
          } else {
              return $.getScript(url, function(){
                  scriptArray.push(url);
              });
          }
      }
      //the file is already there, it does nothing
      //to support as of jQuery 1.5 methods .done().fail()
      else{
          return {
              done: function () {
                  return {
                      fail: function () {}
                  };
              }
          };
      }
  }
}());

/*#####################################################################*/
/*#####################################################################*/


//TEST - tries to load the same jQuery file twice
var jQueryURL = "https://code.jquery.com/jquery-3.2.1.js";
console.log("Tries to load #1");
getScriptOnce(jQueryURL, function(){
  console.log("Loaded successfully #1")
});

//waits 2 seconds and tries to load again
window.setTimeout(function(){
  console.log("Tries to load #2");
  getScriptOnce(jQueryURL, function(){
    console.log("Loaded successfully #2");
  });
}, 2000);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
João Pimentel Ferreira
  • 14,289
  • 10
  • 80
  • 109
-2

Don't do this; don't treat the <script> tag as your compiler loading your source code. Instead, concatenate all your source code together using a javascript minifier, and only hit the server once.

Loading one 4k file is better than loading four 1k files, and the minifier can probably do a better job than that. You shouldn't need it anyway- your browser is caching these modules anyway and unless you mess up your server config, it will do the right thing.

If you really want to continue, first look at demand-loading javascript libraries like Ensure, or Ajile or Dojo, or JSAN. At least then, you're not reinventing the wheel.

Still want to do it yourself? You can either check for something that contacts.js does, or use a variable to test whether you've done it. If contacts.js has something like this in it:

function ContactsLoaded(){...}

You can do:

if(typeof ContactsLoaded=='undefined')$.getScript('contacts.js');

Alternatively, you can set it yourself and make a loader function:

function getScriptOnce(f){
  var a=getScriptOnce;
  if(typeof a[f]=='undefined')$.getScript(f);
  a[f]=true;
}
geocar
  • 9,085
  • 1
  • 29
  • 37
  • 1
    Why use on-demand loading instead of concat? Say, you have a fat 30K (min) js powering a plugin. Chances, that user comes across this plugin while browsing is like 1-to-20. Do you think that I should pre-load it? Don't you think, that on-demand load makes more sense? – Jeffz Jun 30 '13 at 17:26
  • Your point is a good one, but this question is about demand-loading separate modules (see the comments under the question), which means that most users are going to see most modules. What you suggest can be solved with a polite load of a fat backend and it is *very* easy to do -- just include the script tag and let the browser cache it like they normally would. – geocar Jul 06 '13 at 10:08
  • I did not criticise you, just making a pointer (for everyone who reads) that every tool (incl. concat) must be chosen with job in mind. – Jeffz Jul 06 '13 at 11:29