Obtrusive JavaScript

Perhaps the most commonly asked question that beginners have about JavaScript is how to get two scripts to work together on the same page. They already know that each script works separately but when they put both in the same page one or both of the scripts stop working.

The cause of this type of problem is that the scripts are poorly written and are obtrusive. They place values in the global namespace that are overwritten by values placed there by the other script or they try to attach processing to the same event handler and have one overwrite the other. I have even seen examples where the person combining the scripts has put multiple copies of the same JavaScript framework library into the page which will significantly slow the loading of the page unnecessarily and may even be the cause of one of the scripts not working if the two copies are different versions of the library.

Fixing all JavaScript so that it will be as unobtrusive as possible and so allow as many scripts as you like to run in the same page just requires that you follow four simple steps. You should apply these changes with the script as the only JavaScript in the page so as to make testing the script to make sure that it still works as you change it easier without having to worry about any potential interference from other scripts. You can combine the scripts into the same page after you have rewritten them to make them unobtrusive.

Note that obtrusive/unobtrusive is a relative term and each of the following steps if carried out will make the script less obtrusive than it was prior to carrying out the step. It is not possible to make a script completely unobtrusive as then it will have no way to interact with the web page at all.

Step One

The first thing you need to do to make your script less obtrusive is to move all of the JavaScript currently between <script></script> tags into separate files and add a src attribute to the script tag that references the external file instead. This will make it easier to use the script in multiple web pages and will also mean that the page will load faster for those visitors who don't have JavaScript as their browser will not need to load the script that isn't going to be run.

So if our HTML contains the following with the JavaScript comment representing some JavaScript hard coded in the HTML we would copy all of the JavaScript code from inside the script tag (not the script tag itself, just what is inside it) and save that as a separate file giving it a name that indicates what the script does.

<script type="text/javascript">
// some JavaScript
</script>

If the JavaScript represented by that comment is saved into a file called myscript.js then the final HTML at the completion of this step will be:

<script type="text/javascript" src="myscript.js"></script>

If you are using a framework then this will already be in an external file but with common frameworks we can go one better and change the src attribute to reference the framework from a common repository which will increase the likelihood that our visitor already has a copy of the framework cached on their computer and so can avoid downloading it again. The following values can be substituted into the src attribute in order to use a copy of the framework hosted by Google (note the version numbers shown are the latest at the time of writing - you should substitute the latest version of the library if a more recent version exists).

Step Two

The next thing to do is to search through your HTML looking for any "attribute" that starts with 'on'. These are in fact not attributes but are JavaScript event handlers. To completely remove all the JavaScript from the HTML (except for the script tags themselves) we need to replace these within the HTML with an id (if the tag they are attached to doesn't already have one) and then add equivalent event handler code into our external script file.

If we start with the following obtrusive event handler:

<a href="nextpage.htm" onclick="window.open(this);return false;">

Then our HTML after the change will be:

<a href="nextpage.htm" id="myid">

The JavaScript we add to our external file to produce the same event handler is:

document.getElementById('myid').onclick = function() {window.open(this);return false;}

One exception to doing the above is where the event handler is attached to the body tag. With those you don't need to add an id, you simply reference the event handler attached to the window object. For example:

window.onload = function() {dosomething('start');}

Two things to note about making this change. First JavaScript is case sensitive and all the event handlers are entirely lowercase. HTML is not case sensitive and so some people mistakenly capitalize letters in the event handler name in the HTML. When you move the event handler into the JavaScript you must use the correct lowercase version of the name. Second the script that has the event handler in it must be attached to the bottom of the web page so that the HTML with the id is loaded before you try to run the event handler. Attaching scripts to the bottom of the web page just before the </body> is the most efficient place to attach most JavaScript.

Step Three

At this point our JavaScript is completely separate from the HTML. There are still ways in which separate scripts can interfere with one another.

Event handlers are somewhat limited in how they work. You can only have one piece of code attached to a particular event handler that is attached to a specific element in the page at any one time. Trying to attach a second piece of code to the same event will overwrite the first. This is a common cause of script clashes where the two scripts both try to attach code to the onload event handler. The only way to get both to work using event handlers is to include the code for both in the handler but that would then create problems when you only want to use one of the scripts in the page.

The solution is to replace your event handlers with event listeners instead. These are more flexible in that you can have any number of listeners attached to the same event on the same element and you can also detach them again once they are no longer required.

To apply this change we need to first move the anonymous function attached to our event handler and give it a name so that we will be able to more easily reference it from the listener.

newwin = function() {window.open(this);return false;}

Next since older versions of Internet Explorer run jScript instead of JavaScript we will need a small piece of code that will select between the JavaScript event listener and jScript's equivalent called attachevent.

if (window.addEventListener)
addEvent = function(ob, type, fn ) {
  ob.addEventListener(type, fn, false );
};
else if (document.attachEvent)
addEvent = function(ob, type, fn ) {
  var eProp = type + fn;
  ob['e'+eProp] = fn;
  ob[eProp] = function(){ob['e'+eProp]( window.event );};
  ob.attachEvent( 'on'+type, ob[eProp]);
};

we can now replace the event handler call itself with a call to the addEvent function added by the above code that takes care of the differences between the event listener and attachEvent versions. You might also get the code to allow you to removeEvents as well.

addEvent(document.getElementById('myid'),
  'click',
  newwin);

The one other change you'll need to make is that return false which allowed us to prevent the default HTML action from being carried out doesn't work with event listeners. We'll need a simple function to handle this for us as well:

stopDef = function(e) {
if (e && e.preventDefault)
  e.preventDefault();
else if (window.event && window.event.returnValue)   window.eventReturnValue = false;
};

With that code added we then simply replace the return false at the end of our function with stopDef(e) in order to stop the default action.

Step Four

The one way remaining in which scripts can interfere with one another is if they attempt to use the same global variables for different purposes.

If you examine any of the decent JavaScript frameworks you will find that they have the entire script wrapped inside a function so that only one or two variables actually exist in the global namespace. For example the Prototype library only has $() accessible from outside of the framework and all interactions with the framework use that. The jQuery library has jquery() exposed as the way to interact with that framework but also offers $() as a shorter alias that provides the same access. There is also an option inside jQuery to turn off that alias so that the jQuery library can coexist with other libraries that also use $() - such as Prototype.

JavaScript frameworks need to have one variable in the global namespace in order to be able to share one copy of the library between multiple scripts. Your own scripts do not need to have any global variables at all.

Note that with the exception of framework libraries there shouldn't be any need for a script to need more than one JavaScript file. If the script you are trying to make less obtrusive uses more than one file then the script probably uses antiquated JavaScript statements. Antiquated script that include document.write statements and other similarly outdated ways of interacting with the web page must first have that code rewritten to use more modern ways of interacting with the page before you will be able to remove the script from the global namespace. In most cases anything done using document.write statements really ought to be done using a server side script that generates those parts of the page as a part of the original HTML rather than applying those parts of the HTML just for those with JavaScript enabled.

Provided that your script is now all in the one file you can now remove the entire script from the global namespace by adding the following two lines of code. The first line goes at the top of your JavaScript file and the second line goes at the bottom of the file.

(function() {
// your entire JavaScript goes here
})();

To make absolutely certain that we have actually defined all of our variables and that they are all therefore now local to this new anonymous self executing function we can add one extra line at the top of the script.

(function() {
"use strict";
// your entire JavaScript goes here
})();

With this wrapped around our code the entire script will refuse to run in modern browsers if the script uses any code that might potentially cause conflicts with other scripts such as trying to use a variable without defining it first (which without the "use strict" would place that variable in the global namespace even with our script wrapped inside a function.

If you perform all the above steps and test that your script works in all the different modern browsers while in a web page by itself, you can be certain that it can be combined with as many other scripts that have also had the above changes applied together in the same page with all of the scripts being able to run completely independently without interfering (unless of course the actions that the scripts are trying to perform would always clash (such as scripts trying to move the same element in opposite directions at the same time).

 

This article written by Stephen Chapman, Felgall Pty Ltd.

go to top

FaceBook Follow
Twitter Follow
Donate