JavaScript is a very powerful tool to add dynamic effects to your web-site, and to build dynamic content (DHTML).
In recent years there have been vast improvements in the speed and efficiency of JavaScript engines: Mozilla released a number of demonstration web-pages in which complete applications were converted from C to JavaScript (for example the original demo version of the Doom game), which delivered impressive performance, albeit with reduced screen resolution - no longer available due to copyright/licensing issues). The current Mozilla demos are here.
If you look at the error logs of JavaScript on many web-sites, you will often see lots of errors and warnings. If you inspect code on some sites, you will see lots of examples of bad coding. This guide is intended to help you avoid these, and to aid you in writing error free, portable and high performance code.
When coding in JavaScript it is important to understand the concept of its execution threads.
There are two basic ways that script code gets executed in JavaScript:
When a JavaScript file is loaded (read into a web-page), there is code that executes immediately. This code can be:
HTML documents generate a variety of events when certain things happen:
More than one event may fire at once (e.g. onclick and onmouseup occur together), and DOM elements may have more than one event handler for the same event (if you use the addEventListener(), instead of simply assigning a value to the relevant event handler property). When coding event handler functions, you need to be mindful of the fact that multiple threads may be executing simultaneously.
Coding is much easier if you do some design before you start coding. Just because a program is small doesn’t mean that it shouldn’t be designed first.
What kind of design is needed (i.e. what design perspective you use to specify your code) depends on what the code is meant to do. Useful design notations include:
How much (to what detail) you design is also dependent upon how complex/large your code will be. For many smaller JavaScript programs, one diagram is all that is needed.
Use meaningful names. JavaScript names can be long, so use this feature to make your code clearer.
Avoid using reserved names (e.g. “class”, "for", "if", "new", "Array", etc.). Names might be reserved for DOM objects, or for JavaScript built‑ins, and if you use them for your variables or functions, you will get run‑time errors.
The convention is that variables (including objects) are named beginning with lower case, and classes beginning with a capital. To improve readability, words within a name should begin with capitals. So, for example, you would use “WindowController” for a class name (the name of the function which creates a object), “windowController” for the name of an object (an instance of a class), and “tempString” for the name of a String variable.
Each window has its own scope: its own namespace. If you want to access objects in another window (within the same domain), you will need to get hold of that window object first (e.g. to access an object containing the selected language for the site, which you declared in the parent window, you might write var language = window.parent.language; ).
Only declare variables and functions at the global (i.e. window) level if absolutely necessary. Non-global variables are declared using the "var" keyword (e.g. the first time you use a variable name, prefix it with "var": var myMessageString = "Welcome to my web-site" ;).
Variables (i.e. objects) used inside a function should normally be declared only locally (using the var keyword): this avoids interference between different functions which use the same variable names. This is especially important for loop counters inside event handlers; if you have a loop using a counter "i" in two different event handlers that may be executed simultaneously, one event handler may overwrite the value of the other's loop counter (so use loops like var for (var i=0; i<myArray.length; i++) {.....};, or use unique and meaningful names for your loop counters). Do not use global variables to pass data into a function (parameters exist for this), and do not pass results from a function back to the calling code via global variables (if you have multiple data items to pass back, return them inside an object). You can also pass results back to the calling code from a function via the parameters with which it was called, as below.
function removeEmptyStrings(myStringArray) {
var numRemoved = 0 ; // Initialise the count of elements removed to zero.
for (var i=0; i< myString; i++ ) { // Loop over all elements of myStringArray.
if ( myString[i] == "" || myString[i] == null ) { myString = myString.splice(i, 1) ; numRemoved++ } // Updates the reference myStringArray, which can then be accessed by the calling code.
}
return numRemoved ; // Passes the number of empty strings removed from myStringArray back to the calling code.
}
You can also add functions as methods to DOM elements, which can sometimes be very useful.
Overall, treat each namespace (especially the window namespace) as a rare resource, and don’t waste it.
In JavaScript, all so-called variables are actually references. In most cases the programmer doesn't need to be concerned with this, but there are some cases where it matters, and care is needed.
Consider the following code fragments:
In the first example, an anonymous literal string is created, containing "You are not authorised to access this resource!", and a variable unauthString, containing a reference to that literal string, is created.
In the second example, an anonymous number is created, of value 0, and a variable loopIndex, containing a reference to that number, is created.
In the third example an anonymous instance of the class Array is created, and a variable myArray is created, containing a reference to that object.
The fourth example is the same as the third, except that the class used is a custom class, rather than a class provided in the JavaScript language.
You can perform operations on references just as if they were real variables, as below:
In each of the above, the operation is performed on the literal to which the reference points, creating a new literal pointed to by the reference/variable. The old literal is then removed by garbage collection, as described in the next section.
The example below is a case where it does matter that the variables are references:
tdNavContext.ngShowToolTip = makeNgToolTip("Show the Navigational Context (where you are in the <br>menu/page structure) and breadcrumb navigation.", "toolTip", null, "ShowNavContextToolTip_ID") ;
tdNavContext.ngHideToolTip = makeNgToolTip("Hide the Navigational Context and breadcrumb navigation.", "toolTip", null, "HideNavContextToolTip_ID") ;
tdNavContext.ngToolTip = tdNavContext.ngHideToolTip ;
tdNavContext.ngShowToolTip and tdNavContext.ngHideToolTip are properties of tdNavContext (a <td> element, in this case), which are references to objects (<p> elements) created by the makeNgToolTip(...) function. tdNavContext.ngToolTip is a reference to tdNavContext.ngHideToolTip (itself a reference). This means that I can change the value of tdNavContext.ngToolTip without changing tdNavContext.ngHideToolTip, so that I can dynamically change which tool-tip is displayed when mousing over tdNavContext. If I had declared things in reverse, as below, creating tdNavContext.ngToolTip using makeNgToolTip(...) and defining tdNavContext.ngHideToolTip as a reference to it, then changing tdNavContext.ngToolTip would change tdNavContext.ngHideToolTip, and the original value of tdNavContext.ngHideToolTip would be overwritten and get cleaned up by garbage collection.
tdNavContext.ngShowToolTip = makeNgToolTip("Show the Navigational Context (where you are in the <br>menu/page structure) and breadcrumb navigation.", "toolTip", null, "ShowNavContextToolTip_ID") ;
tdNavContext.ngToolTip = makeNgToolTip("Hide the Navigational Context and breadcrumb navigation.", "toolTip", null, "HideNavContextToolTip_ID") ;
tdNavContext.ngHideToolTip = tdNavContext.ngToolTip ;
JavaScript includes automatic garbage collection, which cleans up (deletes) all data (literals and references) to which there is no longer a reference.
In the example in an earlier section of the function removeEmptyStrings(myStringArray), numRemoved is passed back to the calling code as a return parameter, and myStringArray is a reference which is in the scope of the calling code (and therefore neither are cleaned up by garbage collection); the loop index i, however, is local to the function, and not returned to the calling code, and so is destroyed when the function exits.
Provide comprehensive and clear comments, at both the block and the line level. Do this even if you think that no‑one else will ever edit your JavaScript: if you need to change your code in 3 years’ time, you might be thankful for the comments.
var scrollElement = document.getElementById("scollElement_ID"); // get the element containing the scrolling control icon
function findHeaders(startAtHeaderNumber, endAtHeaderNumber, headerList, inElement) {
if ( ! endAtHeaderNumber) { var endAtHeaderNumber = 6 ; } ; // Default endAtHeaderNumber is unassigned
if ( ! startAtHeaderNumber ) { var startAtHeaderNumber = 1 ; } ; // Default startAtHeaderNumber is unassigned
if ( ! headerList ) { var headerList = new Array() ; } ; // If no recursing, create a new headerList
if ( ! inElement ) { var inElement = document.body ; } ; // If search scope not defined, search the document
var re = /HEADER/i ; // Define the regular expression
// Iterate through all nodes in search scope
for ( var i=0; i<inElement.childNodes.length ; i++ ) {
var thisNode = inElement.childNodes[i] ;
// If matches search criterion
if ( thisNode.nodeName.match(re) ) {
var headerNumber = parseInt(thisNode.className.charAt(1)) ;
// If the header number is in range, add to the list
if ( headerNumber <= endAtHeaderNumber && headerNumber >= startAtHeaderNumber ) {
headerList[headerList.length] = thisNode ;
} ;
} ;
// If there are childnodes, recurse down the tree
if ( thisNode.hasChildNodes() ) {
findHeaders(startAtHeaderNumber, endAtHeaderNumber, headerList, thisNode) ;
} ;
}
}
Using proper indenting makes code much easier to read, understand and maintain.
Here is an example of some properly intended JavaScript.
A common error in JavaScript code is a mismatch between the number of opening and closing brackets (parentheses - (), {} and [] ) in a nested (statement inside another statement) statement. Properly indented code makes such errors much easier to find.
There are a wide range of editors available which understand the syntax rules of various coding languages. This will help you to write correct code.
My personal favourite is BlueFish, which is available for both Linux and Windows, and copes with a wide range of languages (JavaScript, Perl, HTML, CSS, SQL, etc.).
Recursion is a very powerful technique in languages like JavaScript. It is very useful, for example, for:
In many cases recursion is much more compact and easier to debug than looping. Use it where appropriate., but with one caveat: always include a limit on the number of recursions that can be made, to prevent runaway recursion (as shown in this example).
function __setInfoPopUpPosition(recursionCount) {
if ( ! rCount ) { var recursionCount = 0 ; } // If recursionCount not a parameter, declare it as 0
if ( recursionCount == 11 ) { return ; } // If already 11 tries, terminate recursion
recursionCount++ ; // Increment recursionCount
// If the objects we need are defined, initialise the position
if ( ( this.infoPopUpElement != null ) && ( this.getRegisteredWindowByName('DF_Main') != null ) ) {
var mainFrameWin = this.getRegisteredWindowByName('DF_Main') ; // Get the main window
var mainFrameEl = mainFrameWin.parent.document.getElementById("DF_MainFrame_ID"); // Get the frame element containing it
var defaultWin = mainFrameWin.parent.parent ; // Get the default window
this.infoPopUpElement.style.top = mainFrameEl.offsetTop+20 ; //Set the top offset
this.infoPopUpElement.style.left = defaultWin.document.getElementById("DF_Outer_ID").offsetLeft+10 ; // set the left offset
}
// Otherwise recurse with a 1 second delay
else { var popupPosTimer = window.setTimeout("siteControllerObject.setInfoPopUpPosition("+recursionCount+");", 1000) ; }
}
Object Oriented Programming (OOP) can be used for virtually every kind of JavaScript. Try to start out using OOP; it is fairly hard to restructure an existing program to make it Object Oriented, but easy to create something that is Object Oriented.
If you are using code from another coder which is not object oriented, what you can do is write an object oriented wrapper for that code. This is usually fairly easy to do, and improves the ease of use greatly.
function windowHashObject() {
this.windows = new Array() ;
function __add(win) {
if ( typeof(win) == "object" && win.name ) { // If parameters correct
var index = this.getIndexOf(win.name) ; // Test if a window of that name already exists
if ( index == -1 ) { this.windows[this.windows.length] = win ;} // Add new
else { this.windows[index] = win ;}; // Replace old
};
};
function __get(winName) {
if ( winName && typeof(winName) == "string" ) { // If parameters correct
var index = this.getIndexOf(winName) ; // Look up the index
if ( index != -1 ) {return this.windows[index] ; }; // Use the index to look up and return the window object
};
return null ; // Otherwise return null
};
function __getIndexOf(winName) {
for ( var i=0; i<this.windows.length; i++) { // Iterate through the array
if ( this.windows[i].name == winName ) {return i ; }; // If a window exists with that name, return the index
};
return -1 ; // Otherwise return -1
};
// Declare the functions as methods
this.add = __add ;
this.get = __get ;
this.getIndexOf = __getIndexOf ;
return this ; Return the new object
}
In JavaScript it is very easy to use a functional coding style. This is fully compatible with OOP.
// Get a list of all elements having the same class name as the element of ID "myElementId"
var myClassList = document.getElementsByClassName(document.getElementById("myElementId").className);
There are a few things that shouldn’t be object oriented, or for which OOP brings no advantages, as described in the sections below.
If you have event handlers (e.g. handlers for onmouseover, onmouseout, and onclick events), you probably want to declare them at the global (window) level.
You can, of course, declare event handlers inside an object, and declare them as methods of that object. If you want an event handler to have variant behaviour (based on which object it is part of, or the value of a variable inside the object and which may change over time), then this is probably the best approach. If not, I recommend declaring them globally (at the window level).
You will also need to write any initialisation code (e.g. for creating objects) at the global (window) level. Even so, encapsulate as much as possible in a function, although you should declare any global variables used/set in the initialisation outside of the initialisation function. This way, it is very easy to see what variables are declared at the global level.
Basically, nearly everything else (i.e. not listed in the sections above) should be object oriented.
What I tend to do is to create a SiteController object, which contains variables, methods, special event handlers and general purpose functions. The SiteController object is created (an Instance of a Class created) in the initialisation code. I usually declare the SiteController at the highest level (in the namespace of the top-level window), and the initialisation code used in any sub-windows (frames) creates a reference to the SiteController object in each frame.
Many people complain that JavaScript is not very Object Oriented; in particular, that it does not support multiple inheritance. This criticism is unfair.
Whilst JavaScript does not directly provide a means to implement multiple inheritance (it doesn't do it for you), it is very easy to implement it yourself. Each object in JavaScript contains a property object (myObject.properties) which includes all the sub‑elements of that object: variables and methods. It is trivially easy to write a piece of code which copies all the properties of one object to another; actually JavaScript makes new references to objects, rather than copying them. Because copies are references to the originals, unless you take care to make a real copy, you only need to do a shallow copy (only top‑level properties, not sub‑objects).
function dynamicMenuObject(defaults) {
// Initialise
if ( ! defaults ) { var defaults = new dynamicMenuDefaults() ; }
else { defaults = new dynamicMenuDefaults(defaults) ; }
for ( property in defaults ) {
this[property] = defaults[property];
} ;
...
...
}
JavaScript allows you to directly access properties (variables and functions) inside an object directly (e.g. myObject.myVariable and myObject.__myFunction() ); in other words it doesn't enforce the rules of object orientedness. Don’t do this, as it defeats the whole point of OOP.
Instead, provide methods to access functions (myObject.getMyVariable() and myObject.setMyVariable() , where getMyVariable and setMyVariable are declared as methods of the object). This allows you to change the implementation of how data is stored without changing the interface to that data (how the data is accessed from other objects). Also, use only the declared methods, not the internal function.
Methods are created by declaring a function inside the class definition (e.g. __myFunction() ) and then declaring that function as a method (exporting it: this.myFunction = __ myFunction; ).
Code inside the object can access variables inside the object with less risk, so you can write this.popUpElement rather than this.getPopUpElement() when accessing the data from elsewhere in the object (from within functions within the object).
JavaScript is widely (but not exclusively) used in web pages.
DHTML is “Dynamic HTML”, whereby the JavaScript either modifies or creates elements in the DOM (Document Object Model), by calling methods built into the DOM, and accessing the properties of elements. Examples include:
JavaScript is also used to define actions to be performed when a element is clicked (e.g. clicking a menu entry), during mouse roll-over, or when text is entered into a form element (validation code, for example to check that an email address has the right structure, and that a phone number is long enough).
All of the above is achieved by accessing the DOM: either methods, or objects/elements.
Before you start to access the DOM, check the standards. The W3C standards are available online. There are differences in the degree of compliance (not only from one browser to another, but also between different browser versions), and if you want your web‑pages to be viewable in multiple browsers and versions you will need to accommodate them.
Because of the differences between different browsers, especially the degree to which they are standards‑compliant (although Microsoft might rather say differences regarding whose standard they comply to), you can easily write things that work in one, and not in another.
Some coders solve this problem by first determining which browser and version is being used, and then using different code to access the DOM depending on what the answer is. This is very dangerous, because:
The proper way to solve this problem is to test for the existence of an object or method in the DOM before trying to use it. You don’t need to do this for every DOM access, but there are some areas where compliance is poor, or where the standards have changed over time, and in such cases the “test and then use” method is the safest.
A good example is event handling. In W3C compliant DOM implementations, event handlers are called with an event object as a parameter to the event handler function call. You generally need this object to find out what was clicked, or which key was pressed. In Internet Explorer, however, to get hold of the event object you need to access window.event. This has the disadvantage that if another event occurs before you access the event object, it can be overwritten. The example below shows how to write an event handler which works out whether or not it has been given an event object, and if not tries to get hold of window.event.
function tipMouseOut(evt) {
if ( ! evt ) { var evt = window.event } ; // Handle Internet Explorer event
if ( ! evt.target ) { var target = evt.srcElement } else { var target = evt.target } ; // Handle Internet Explorer event
var tip = target.tip ; // Get the tool-tip Element
tip.style.visibility = "hidden" ; // Set the visibilty to hidden
tip.style.top = "0px"; // Set the position
tip.style.left = "0px"; // Set the position
return true ;
} ;
This principle can be applied to all objects and methods in the DOM. For example, before calling document.getElementById(‘myElementID’), you could test whether it exists with
if (document.getElementById) then {…}.
else {…}.
In fact, document.getElementById has been a stable part of everyone’s DOM implementation for a long time, so you shouldn’t need to use this approach in the case of this method, but there are other cases where this is not true.
Because of the difference in the way IE handles events (see the description about accessing the event object in the section above), there is an annoying limitation with IE, regarding where event handlers must be declared.
In other browsers, you can declare an event handler function in one window, and use it in another window. For example, you can use document.getElementById(‘myLikeIconId’).onclick = window,parent.likeIconClickHandler ; In IE, this will typically not work, because the event handler will try to get the event object from the wrong window.
The Outline style is a useful means of temporarily highlighting an element, for example in help pages, to show where the described element is in another page/frame. In older versions IE, this style is not supported.
When creating HTML elements dynamically using JavaScript, it may be tempting to define the appearance and layout on a per element basis, by adding a “style” properties to each object. This is not a good idea.
Instead, use styles from a stylesheet (CSS: Cascading Style Sheet). This is done by defining the “className” property of an object. An object can have multiple classes (but as a list, all in one element property, but not multiple className properties).
Using centrally defined styles allows you to change the appearance of a whole page simply by changing the definition of the style (or by overriding it: defining a style in a more local – less global–scope which will supersede the global style for those elements in the same scope). Changing and overriding styles can easily be done by accessing the DOM from JavaScript (your DOM will contain not only all the built‑ins, but also everything defined in your HTML file, including everything in linked/included files such as stylesheets).
It is possible to use JavaScript to dynamically create stylesheets in your web-document, rather than importing a CSS file. It is also possible to modify stylesheets which are imported or explicitly declared in the page.
Adding methods and objects to elements (DOM Element Objects) is a powerful technique. For example:
This used to be a major problem. It was often hard to find out what was going wrong with a piece of JavaScript.
Nowadays, many browsers include (often as an installation option) some diagnostics. What this doesn't give you is diagnostic logging; you get error logging and the ability to inspect the DOM properties.
Some people use window.alert(); statements in the JavaScript. The problem with this is that any visitors to your web-site will see the alert pop-ups, and need to click them away. It is fine for code which is not yet published (live), but not suitable when making changes to an existing live site. Having a test and development domain when modifying your JavaScript avoids this problem, but not everyone is able to arrange this.
To provide diagnostic logging, I use a logger, written in HTML and JavaScript. It is available to download from this site (www.fosberry.com). The code is very old, and I plan to improve and update it soon. It supports logging at 4 priority levels, with filtering/masking of the display based on level, so that you can track the execution path of code, and view intermediate variables, using logging statements in the JavaScript. If logging is not enabled (done via the URL) the logging statements do nothing.
Copyright, unlike patents, is automatic, and it is not necessary to register and pay a fee to have copyright on your code. Nevertheless, you should always include a copyright statement as a comment at the start of each JavaScript file, which makes it clear who owns the code, and under what terms it may be reused or licensed.
JavaScript on a web-site is not secure: any visitor may view and download it. There is little point in being restrictive about reuse, as it is not easy to enforce. My own JavaScript is free to reuse as long as my copyright statement is included (if you make modifications, then you will want to add your own copyright information).
All sorts of errors can occur with your JavaScript code, such as:
In functions, check for the existence of (and perhaps also the type of) parameters.
Normally, if a major error occurs in your JavaScript, execution will stop. If you include an onerror event handler at the window level (and it returns true), execution will continue, which can be very useful.
One of the major problems that all web‑developers face is testing. Obviously you want your web page to be accessible to a wide range of browser types and versions, but the number of different variants is enormous.
The problem is not due to differences in the JavaScript, but to differences in the DOMs of the browsers, so whenever you try to access the loaded documents to add dynamic effects you may run into compatibility issues.
Installing a range of browser types (e.g. Firefox, IE, Chrome, Safari and Opera) on one PC is not a problem; generally the installation of one browser does not interfere with the operation of another previously installed browser. What you cannot deal with so easily is testing against different versions of the same browser. You need to have a different machine for each version of IE, for example, installed.
The answer to this dilemma is to use Virtual Machines. A Virtual Machine (VM) is a computer environment running as a program on another. Most desktop and laptop machines produced in the last 5 years have enough performance and storage to run at least one VM at a time. On my small server at home, I regularly run three VMs at a time, and can run more than 10. Using VMs, you can have a range of virtual client machines with different browser versions installed, which you run only when you need to test with that browser version. You only need to start a VM when you want to use it, so the overhead is low. VMs can be cloned from other VMs so you don't need to do a complete operating system installation and configuration for each VM; just the installation of browsers.
Whilst I would not recommend Windows as a host for running virtual machines, it is possible, and Microsoft have a product for it.
For performance reasons, I recommend Linux as a VM host. I have several VMs, both Linux and Windows, which I run on my Linux hosts.
Once you have your various older browser versions installed in your VMs, you will need to disable automatic updates, to ensure that the browsers remain at the desired version.