Native Javascript Ninjutsu: Include External JS

Ninja kanji

Sensei Says (dark ages, to dojos, to disciplines)

Javascript used to be a dark and ancient art, looked down upon by many web developers as a dishonorable – even malicious – ‘copy and paste’ language. Macromedia’s Shockwave – which later became Macromedia Flash, which even later became Adobe Flash – pushed audio, video, and interactive motion graphics onto the web in a cross-browser compatible format that all but decimated the need and appeal for Javascript. What little Javascript community there was began to seriously dwindle and die out.

And then the frameworks came to rise: Dojo, Yahoo! UI Library, Google Web Toolkit, jQuery, Prototype, MooTools, and many more. With these powerful armies by its side, the Javascript community quickly grew and regained its honor, competing heavily with the fluid animation and complex, real-time interactivity that Flash had delivered for years.

Javascript now seems to be a strong, healthy, and widely accepted language, frequently used and relied upon by web developers across the land. Yet how many of today’s programmers can write pure native Javascript without the aid of a framework? How many can perform AJAX requests without a framework? And most importantly, how many can craft fully cross-browser compatible code without a framework? In order to not become dependent on the frameworks – and thus risk sliding backwards into the dark ages – we must maintain a wide variety of practices: these are the native Javascript disciplines.

Discipline 3: Shinobi-iri (stealth and entering methods)

Using native JS to include external scripts is pretty fun and easy to do. Here’s a simple example below:

[html]
<html>
<head>
</head>
<body>
<script id="first-script" type="text/javascript">
(
function ()
{
var s = document.createElement( ‘script’ );
s.id = ‘second-script’;
s.type = ‘text/javascript’;
s.async;
s.src = ( ‘https:’ == document.location.protocol ? ‘https://’ : ‘http://’ ) + ‘domain.com/second-script.js’;
var x = document.getElementById( ‘first-script’ );
x.parentNode.insertBefore( s, x );
}
)();
</script>
</body>
</html>
[/html]

As you can see, the HTML file has a script tag in it that contains an anonymous function. The anonymous function is wrapped in parentheses and followed by (); so that it will execute itself immediately. The reason we do this is so that the variables used are explicitly scoped within this function only. This prevents possible overlap from any other JS variables that may exist in the HTML. While this may seem to be a bit paranoid for smaller projects, I’m often asked to complete JS missions which involve including external scripts into third party, client-owned pages, and thus I have no control over – and sometimes no idea about – what may already exist in the page.

The anonymous function starts by creating a script element and saving it to a variable (s, in this case). The element is given a handful of attributes: an id, a type, an async value, and a src. These should all be pretty straightforward and self-explanatory, though the aync attribute is a bit interesting. The attribute sets the script to load asynchronously, and it works the same as the disabled and checked attributes, in that it’s value doesn’t matter, but only it’s presence holds meaning. If the async attribute exists it will automatically be considered to hold the value of true. Considering this, you don’t need to actually set the async attribute to any value, but instead you can simple define the attribute and it will work as expected. Keep in mind that the async attribute is a new HTML5 attribute and so it isn’t supported in all browsers yet.

After we set all the proper attributes for the element we then find the current script and insert our new script directly before it. Alternatively, if we’d like to insert our new script directly after the current script we can do the following:

[html]
<html>
<head>
</head>
<body>
<script id="first-script" type="text/javascript">
(
function ()
{
var s = document.createElement( ‘script’ );
s.id = ‘second-script’;
s.type = ‘text/javascript’;
s.async;
s.src = ( ‘https:’ == document.location.protocol ? ‘https://’ : ‘http://’ ) + ‘domain.com/second-script.js’;
var x = document.getElementById( ‘first-script’ );
x.parentNode.insertBefore( s, x.nextSibling );
}
)();
</script>
</body>
</html>
[/html]

If for some reason you can’t – or don’t want to – give the first script an id attribute then you can include the second script like so:

[html]
<html>
<head>
</head>
<body>
<script type="text/javascript">
(
function ()
{
var s = document.createElement( ‘script’ );
s.type = ‘text/javascript’;
s.async;
s.src = ( ‘https:’ == document.location.protocol ? ‘https://’ : ‘http://’ ) + ‘domain.com/script.js’;
var x = document.getElementByTagName( ‘script’ )[0];
x.parentNode.insertBefore( s, x );
}
)();
</script>
</body>
</html>
[/html]

This will return an array of all the script tags in the DOM. It will then save the first element in this array to the variable x. Sometimes this will be the current script tag, but other times (depending entirely on the HTML structure of the page) this could be earlier script tags. The point of this method is to make sure the new JS is included to the page in an area that makes the most sense for the page owner. This can be very important when working with third party, client-owned websites.

Cross-browser compatibility is a big deal and even though it seems like everyone is browsing with JS enabled these days, I believe it’s still important that we make things work for those who aren’t. In most circumstances where I’m forced to use native JS to include scripts in websites it’s very likely that these scripts will be used to trigger server-side code. The end client may not always allow me to include server side code directly in their website itself, so a helpful workaround is to load the server side code into an img tag and wrap this in a noscript tag:

[html]
<html>
<head>
</head>
<body>
<script type="text/javascript">
(
function ()
{
var s = document.createElement( ‘script’ );
s.type = ‘text/javascript’;
s.async;
s.src = ( ‘https:’ == document.location.protocol ? ‘https://’ : ‘http://’ ) + ‘domain.com/script.js’;
var x = document.getElementByTagName( ‘script’ )[0];
x.parentNode.insertBefore( s, x );
}
)();
</script>
<noscript>
<img src="http://domain.com/server-side-script.php" width="1" height="1" />
</noscript>
</body>
</html>
[/html]

What this will do is execute the server side code instead of actually loading an image. Although this doesn’t allow you to execute JS, it does help to provide a work around and an alternative means of firing third party code when you need your solution to be as cross-browser compatible as possible.

There’s a third way to include JS that I would like to share with you and it involves the use of the HTML iframe tag. The initial JS would look like this:

[html]
<html>
<head>
</head>
<body>
<script type="text/javascript">
(
function ()
{
var s = document.createElement( ‘iframe’ );
s.src = ( ‘https:’ == document.location.protocol ? ‘https://’ : ‘http://’ ) + ‘domain.com/iframe-script.html’;
var x = document.getElementByTagName( ‘script’ )[0];
x.parentNode.insertBefore( s, x );
}
)();
</script>
</body>
</html>
[/html]

The code in iframe-script.html would look like this:

[html]
<html>
<head>
</head>
<body>
<script type="text/javascipt">
// insert your javascript code here!
</script>
</body>
[/html]

There are times when it may make more sense to use an iframe, though I do not often find them that useful, and the above code has some problematic points. For starters, if the initial webpage you are working with has its scripts all in the head tag then you will be attempting to insert the iframe there, which is not a good idea. Furthermore, it is very difficult to pass JS variables and make calls to functions between the parent page and the child iframe within it. In fact, if these pages are either from different domains or even use different protocols (HTTP versus HTTPS) then it will be impossible to communicate between them using JS. To add to the problems the iframe tag is also not supported in all browsers. For these reasons I avoid the use of iframe tags whenever possible.

I hope this article has been helpful in providing you with a way to use native JS to include external scripts into your pages. Stay tuned for next week’s discipline: Cookies and Variables.

Previous Disciplines
Discipline 1: AJAX with XHR
Discipline 2: Dynamic JS with PHP

Are you smart? Innovative? Driven? If you’re interested in working on challenging projects in one of the world’s most fast-paced industries, why not check out the openings on our Careers page?

Back to news overview