Where Marketing, Analytics, and UX meet

Loading JavaScript using JavaScript

two cups of coffee

Recently I wrote a module for Drupal that automagically loads JavaScript based on the page URL or the form you need to affect. But what if your JS needs access to JS in another JS file?

This blog post is about how to load functions or classes from another JavaScript file. Imagine the following scenario:

You have a website, we will call it http://www.website.com, and you want your pages to load quickly. So you decide you want to load the minimum number of resources required to successfully load each page. Now, historically, most people just jam all of their JavaScript into a single file and call what they need on the pages they need to call it. Here is the problem. There are many moments when JavaScript can load into your Web browsers memory, but the bottom line here is this: if you attach the file to the page, eventually all of it gets loaded. Think of the pockets on an outfit. You are going out on the town, so you load up the pockets with anything you might need. By the end of the night maybe you have made use of a few of those items, but in reality you are exhausted from carry them around all night long. Equally as bad, when you needed one of those items, you had to find the item. And heaven forbid later on you need to make changes to any of the items. Which pocket do you go into to find which item?

There is a simpler way to handle this: writing a hand full of scripts rather than one script. "Now, why would I want to do that?" you might ask. Well, for one you can organize your JavaScript in a manner that makes it easier to maintain later (we will get into how that might work in a moment). You might be thinking to yourself, "But then the Web browser has to load a bunch of different scripts and while that is okay for ease of development, it is harder on the Web browser, and isn't there a limitation of the number of resources a browser can load per page?" All of that is true and that is why I would recommend you use some kind of framework to deal with aggregating your javascript before it gets delivered to the browser. "Now you're crazy Steve. I thought this was about having many smaller scripts, well organized, that only load what you need on the pages you need them?" Why yes. That is exactly what I am talking about. I use the Drupal (framework) Web CMS often. And recently I wrote a module that handles loading the right exact JavaScript at the right time, but Drupal itself has the ability to aggregate those scripts. The difference here: it doesn't aggregate all of your scripts and functions. Rather on a page by page basis it loads just the scripts you need but you can tell it to aggregate them into a single download in the moment you need them to be sent with your page. So with that out of the way, let's talk about http://www.website.com and how you might want to load just the necessary functions you need at the right time.

Let's imagine this site is a blog (like the one you are reading now). And each section might have it's own special scripts. For example, imagine http://www.website.com/blog/code and http://www.website.com/blog/design. The "/blog/code" section would have aposts about code development while over on the "/blog/design" area you are writing posts about design.

First, let's imagine that you need a javascript snippet that loads on any page under "/blog". We don't care if the article is at the URL "/blog/design/color-blue" or "/blog/code/weekend-development". Any page from "/blog" or deeper should have the JavaScript load. Now in Drupal, I wrote a module that does this for me automatically. Before each page loads it looks at the URL (i.e. http://www/website.com/blog/code/weekend-development) and it tries to see if there are JavaScript files in the themes "/js/paths" folder that match the current path or a parent path. So if I have a JavaScript file in my theme named "js/paths/blog.js" then it will load that file for any URL that contains "/blog" in the URL. Cool right?

Now imagine I want a slightly different script to run on "/blog/design" pages. In Drupal I would now setup a new file in my theme named "js/paths/blog-design.js" and any page with a URL that contains "/blog/design" in the URL will get that JavaScript file loaded. But what if I want it to load both? Well, now I have a problem. At least in the Drupal module I wrote, it starts searching for ".js" files at the most specific level it is able. This means, if I want a specific page like http://www.website.com/blog/design/color-blue to have it's own JS file, I would name it "js/paths/ blog-design-color-blue.js", but as soon as it finds one, it stops looking for a less specific match. So it will load blog-design-color-blue.js but it will not load blog-design.js or blog.js even if they exist. In some cases that is not a bad thing. But what if you want all of those JavaScript files to load. If you are at the page http://www.website.com/blog/design/color-blue, how do you get the page to load all three JS files if they exist?

jQuery to the rescue.

Here is a little test for you to show you how to make this happen. We will write two javascript files, simulating that one should load the other and an HTML page to load the second JavaScript file and have the second JavaScript file load the first one for us.

Let's write the first file. Create a folder for these pages and call it "jQInclude". Inside that folder create a file and name it "script-1.js" Here is the code for that file:

jQuery(document).ready(function(){
  theFunctionCall("This is from level 1<br/>","test");
});

function theFunctionCall( thisMessage, thisContainer ){
  jQuery("#" + thisContainer ).append( thisMessage );
}

 

Now, save the file. Here is what is going on in this file. first, jQuery checks to see if the HTML file it is loading into is ready but not yet visible to the person looking at it (line #1). Once that is true, it calls the function named "theFunctionCall" (line #2) and gives it a message and also tells it where to put that message. The function itself is defined next (lines #5-7) and it goes to find a <DIV> tag with an ID of "test" (that is the WHERE we asked it to put it) on it.Once it finds that it appends INTO the <DIV> the message (the one we gave to the function, "This is from level 1" and it creates a line break after that message is inserted).

Now let's create a new file in the same folder and name it "index.html". Once you have that add the following content to that new file:

<!doctype html>
<html>
  <head>
    <meta charset=utf-8>
    <title>includes with includes</title>
    <script type="text/javascript" src="http://code.jquery.com/jquery-latest.min.js"></script>
    <script type="text/javascript" src="script-1.js"></script>
  </head>
  <body>
    test results:
    <div id="test"></div>
  </body>
</html>

Once you have saved that, if you were to load this into a browser like Firefox (chrome doesn't like to run external JavaScript resources outside of a Web server, so if you are not using a Web server, then test with Firefox) you will see that jQuery will add your message to the test div. Success! This file symply loads jQuery from a Content Delivery Network (CDN) on the Web (that is line #6) and then it loads your script (line #7) to add the message to the test div on the page (line #11).

Now we will create another JavaScript file in the same folder and name it "script-2.js". Once you have saved that, add the following code to that file:

jQuery(document).ready(function(){
  jQuery.getScript("script-1.js",function(){
    //all of this runs after that bit loads and runs
    anotherFunctionCall("This is a test from level 2!<br/>", "test");
    theFunctionCall("This should really print last, and the function will have loaded!<br/>", "test");
  });
  jQuery("#test").append("This will likely show up first!<br/>");
  // theThirdFunctionCall("This could blow up because the function may not have loaded yet.<br/>", "test");
});

function anotherFunctionCall( thisMessage, thisContainer ){
  jQuery("#" + thisContainer ).append( thisMessage );
}

Now save that. This script is similar to the first one but a little more tricky. At the start it also checks to be sure the page is loaded but not yet displayed the person waiting to see it (line #1). But this time it tries to load "script-1.js" next (line #2). Once script-1.js loads then any code between line #2 and line #6 is executed! Before we look at lines #4 and #5, lets notice lines #7 and #8. Line #7 claims that it will likely load this line first. This is true because even though we try to load the code from script-1.js first in line #2, it will take a moment to load while line #7 runs immediately. Line #8 is a call to that function defined in script-1.js right? In reality, it is a comment. But if we removed the comment marks "//" and tried to run that line, it would throw an error. Why? Because at that moment in the code, any function defined inside script-1.js hasn't yet loaded. Remember that all of the functions defined in script-2 (like the function defined at lines #11-13) as well as script-1.js can be called between lines #2 and #6. This is because we are telling that code to wait until the script loads.

And this is how you load one javascript file into another. If we go back for a final moment and imagine http://www.website.com/blog/design/color-blue and imagine there are the following JavaScript files:

  • blog.js
  • blog-design.js
  • blog-design.color-blue.js

And we want pages that load blog-design.js to also load blog.js and pages that load blog-design-color-blue.js to all load both blog-design.js as well as blog.js, then we simple have to call jQuery's "getScript" function on the previous script to load it. Said another way...

  • blog.js: loads only itself
  • blog-design.js: uses jQuery.getScript to also load blog.js
  • blog-design-color-blue.js: will only need to use jQuery.getScript to load blog-design.js and because blog-design.js already loads blog.jsblog-design-color-blue.js gets all three files and all of their custom functions

And that is how you do that!

Final tip! 

As I was writing this I noticed that each attempt to load an external script (script-1.js) by another script (script-2.js) was loading a fresh copy. Not good right? We want everything to be organized as well as efficient. It ends up that jQuery is, by default, explicitly avoiding using the cache and here is why. The jQuery.getScript function is really a jQuery.ajax wrapper. And the default assumption here is that you intend to get new fresh results with your ajax call, so it does a few tricks to avoid fetching cached results. But there is a solution. You can globally change the cache default for jQuery's ajax. Here is how you do that.

jQuery(document).ready(function(){
  jQuery.ajaxSetup({ cache: true });
  jQuery.getScript("script-1.js",function(){
    //all of this runs after that bit loads and runs
    jQuery.ajaxSetup({ cache: false });
    anotherFunctionCall("This is a test from level 2!<br/>", "test");
    theFunctionCall("This should really print last, and the function will have loaded!<br/>", "test");
  });
  jQuery("#test").append("This will likely show up first!<br/>");
  // theThirdFunctionCall("This could blow up because the function may not have loaded yet.<br/>", "test");
});

function anotherFunctionCall( thisMessage, thisContainer ){
  jQuery("#" + thisContainer ).append( thisMessage );
}

Running this will ensure that if a visitor previously loaded "script-1.js" on another page, and the browser and the Web server were configured to cache the delivery of javascript, then when "script-2.js" attempts to "getScript( 'script-1.js')" it will actually fetch the cached script and not a fresh copy. Line #2 immediately tells jQuery to be willing to fetch a cached version if one is available. And then later on line #5 you tell jQuery to go back to avoiding cached results for ajax. You are basically saying, "Okay, jQuery, get my a cached version of script-1.js, if possible, and once that is loaded go back to avoiding cache again for any other ajax data," which allows you to call for fresh new ajax and get those results uncached.

Add new comment

*

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.
By submitting this form, you accept the Mollom privacy policy.