Article

Home » Client-side Coding » JavaScript & Ajax Tutorials » Script Smarter: Quality JavaScript from Scratch
SitePoint Feature Article

About the Author

James Edwards and Cameron Adams

author_camjames James Edwards (aka brothercake) is a freelance web developer specializing in advanced DHTML programming and accessible web site development. He is an outspoken advocate of standards-based development, a part-time forum moderator, and author of the Ultimate Drop Down Menu system—the first commercial DHTML menu to be WCAG-compliant. Cameron Adams has a degree in law and one in science; naturally he chose a career in web development. His business cards say, “Web Technologist” because he likes to have a hand in graphic design, JavaScript, CSS, PHP, and anything else that takes his fancy that morning.

View all articles by James Edwards and Cameron Adams...

Script Smarter: Quality JavaScript from Scratch

By James Edwards and Cameron Adams

March 8th, 2006

Reader Rating: 9.5

Page: 1 2 3 4 5 6 7 8 Next

JavaScript is an amazingly useful language that offers many unique benefits. With a little consideration for how scripted functionality degrades, you can use JavaScript to bring a whole range of functional, design and usability improvements to your web sites.

This article is actually an excerpt from SitePoint's new title, The JavaScript Anthology: 101 Essential Tips, Tricks & Hacks. The four chapters included here cover:

  • a comprehensive introduction to JavaScript including a tour of basic techniques, debugging, and more
  • the ways in which the Document Object Model can be utilized in JavaScript programming
  • the practicalities of working with frames, including techniques for working with popups, communicating between frames, and getting scrolling position
  • an introduction to basic DHTML, which covers event handling, cursor detection, finding the size and position of an element, and more

If you'd rather read this primer offline, you can download the chapters in PDF format.

But now, let's begin with an introduction to JavaScript, exploring what it's for, and how we can use it.

JavaScript Defined

JavaScript is a scripting language that's used to add interactivity and dynamic behaviors to web pages and applications. JavaScript can interact with other components of a web page, such as HTML and CSS, to make them change in real time, or respond to user events.

You'll undoubtedly have seen JavaScript in the source code of web pages. It might have been inline code in an HTML element, like this:

<a href="page.html" onclick="open('page.html'); return false;">

It might have appeared as a script element linking to another file:

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

Or it may have had code directly inside it:

<script type="text/javascript">
function saySomething(message)
{
 alert(message);
}
saySomething('Hello world!');
</script>

Don't worry about the differences between these snippets yet. There are quite a few ways -- both good and bad -- in which we can add JavaScript to a web page. We'll look at these approaches in detail later in this chapter.

JavaScript was developed by Netscape and implemented in Netscape 2, although it was originally called LiveScript. The growing popularity of another language, Java, prompted Netscape to change the name in an attempt to cash in on the connection, as JavaScript provided the ability to communicate between the browser and a Java applet.

But as the language was developed both by Netscape, in its original form, and by Microsoft, in the similar-but-different JScript implementation, it became clear that web scripting was too important to be left to the wolves of vendor competition. So, in 1996, development was handed over to an international standards body called ECMA, and JavaScript became ECMAScript or ECMA-262.

Most people still refer to it as JavaScript, and this can be a cause of confusion: apart from the name and similarities in syntax, Java and JavaScript are nothing alike.

JavaScript's Limitations

JavaScript is most commonly used as a client-side language, and in this case the "client" refers to the end-user's web browser, in which JavaScript is interpreted and run. This distinguishes it from server-side languages like PHP and ASP, which run on the server and send static data to the client.

Since JavaScript does not have access to the server environment, there are many tasks that, while trivial when executed in PHP, simply cannot be achieved with JavaScript: reading and writing to a database, for example, or creating text files. But since JavaScript does have access to the client environment, it can make decisions based on data that server-side languages simply don't have, such as the position of the mouse, or the rendered size of an element.

What About ActiveX?

If you're already quite familiar with Microsoft's JScript, you might be thinking "but JavaScript can do some of these things using ActiveX," and that's true -- but ActiveX is not part of ECMAScript. ActiveX is a Windows-specific mechanism for allowing Internet Explorer to access COM (the Component Object Model at the heart of Windows scripting technology) and generally only runs in trusted environments, such as an intranet. There are some specific exceptions we'll come across -- examples of ActiveX controls that run without special security in IE (such as the Flash plugin, and XMLHttpRequest) -- but for the most part, scripting using ActiveX is outside the scope of this book.

Usually, the computer on which a client is run will not be as powerful as a server, so JavaScript is not the best tool for doing large amounts of data processing. But the immediacy of data processing on the client makes this option attractive for small amounts of processing, as a response can be received straight away; form validation, for instance, makes a good candidate for client-side processing.

But to compare server-side and client-side languages with a view to which is "better" is misguided. Neither is better -- they're tools for different jobs, and the functional crossover between them is small. However, increased interactions between client-side and server-side scripting are giving rise to a new generation of web scripting, which uses technologies such as XMLHttpRequest to make requests for server data, run server-side scripts, and then manage the results on the client side. We'll be looking into these technologies in depth in Chapter 18, Building Web Applications with JavaScript.

Security Restrictions

As JavaScript operates within the realm of highly sensitive data and programs, its capabilities have been restricted to ensure that it can't be used maliciously. As such, there are many things that JavaScript simply is not allowed to do. For example, it cannot read most system settings from your computer, interact directly with your hardware, or cause programs to run.

Also, some specific interactions that would normally be allowed for a particular element are not permitted within JavaScript, because of that element's properties. For example, changing the value of a form <input>
is usually no problem, but if it's a file input field (e.g., <input type="file">), writing to it is not allowed at all -- a restriction that prevents malicious scripts from making users upload a file they didn't choose.

There are quite a few examples of similar security restrictions, which we'll expand on as they arise in the applications we'll cover in this book. But to summarize, here's a list of JavaScript's major limitations and security restrictions, including those we've already seen. JavaScript cannot:

  • open and read files directly (except under specific circumstances, as detailed in Chapter 18, Building Web Applications with JavaScript).
  • create or edit files on the user's computer (except cookies, which are discussed in Chapter 8, Working with Cookies).
  • read HTTP POST data.
  • read system settings, or any other data from the user's computer that is not made available through language or host objects (Host objects are things like window and screen, which are provided by the environment rather than the language itself.)
  • modify the value of a file input field.
  • alter a the display of a document that was loaded from a different domain.
  • close or modify the toolbars and other elements of a window that was not opened by script (i.e., the main browser window).

Ultimately, JavaScript might not be supported at all.

It's also worth bearing in mind that many browsers include options that allow greater precision than simply enabling or disabling JavaScript. For example, Opera includes options to disallow scripts from closing windows, moving windows, writing to the status bar, receiving right-clicks ... the list goes on. There's little you can do to work around this, but mostly, you won't need to?such options have evolved to suppress "annoying" scripts (status bar scrollers, no-right-click scripts, etc.) so if you stay away from those kinds of scripts, the issue will come up only rarely.

JavaScript Best Practices

JavaScript best practices place a strong emphasis on the question of what you should do for people whose browsers don't support scripting, who have scripting turned off, or who are unable to interact with the script for another reason (e.g., the user makes use of an assistive technology that does not support scripting).

That final issue is the most difficult to address, and we'll be focusing on solutions to this problem in Chapter 16, JavaScript and Accessibility. In this section, I'd like to look at three core principles of good JavaScript:

  • progressive enhancement - providing for users who don't have JavaScript
  • unobtrusive scripting - separating content from behavior
  • consistent coding practice - using braces and semicolon terminators

The first principle ensures that we're thinking about the bigger picture whenever we use a script on our site. The second point makes for easier maintenance on our end, and better usability and graceful degradation for the user. (Graceful degradation means that if JavaScript is not supported, the browser can naturally fall back on, or "degrade" to, non-scripted functionality.) The third principle makes code easier to read and maintain.

Providing for Users who Don't Have JavaScript (Progressive Enhancement)

There are several reasons why users might not have JavaScript:

  • They're using a device that doesn't support scripting at all, or supports it in a limited way.
  • They're behind a proxy server or firewall that filters out JavaScript.
  • They have JavaScript switched off deliberately.

The first point covers a surprisingly large and ever-growing range of devices, including small-screen devices like PDAs, mid-screen devices including WebTV and the Sony PSP, as well as legacy JavaScript browsers such as Opera 5 and Netscape 4.

The last point in the list above is arguably the least likely (apart from other developers playing devil's advocate!), but the reasons aren't all that important: some users simply don't have JavaScript, and we should accommodate them. There's no way to quantify the numbers of users who fall into this category, because detecting JavaScript support from the server is notoriously unreliable, but the figures I've seen put the proportion of users who have JavaScript switched off between 5% and 20%, depending on whether you describe search engine robots as "users."

Solution

The long-standing approach to this issue is to use the HTML noscript element, the contents of which are rendered by browsers that don't support the script element at all, and browsers that support it but have scripting turned off.

Although it's a sound idea, in practice this solution has become less useful over time, because noscript cannot differentiate by capability. A browser that offers limited JavaScript support is not going to be able to run a complicated script, but such devices are script-capable browsers, so they won't parse the noscript element either. These browsers would end up with nothing.

A better approach to this issue is to begin with static HTML, then use scripting to modify or add dynamic behaviors within that static content.

Let's look at a simple example. The preferred technique for making DHTML menus uses an unordered list as the main menu structure. We'll be devoting the whole of Chapter 15, DHTML Menus and Navigation to this subject, but this short example illustrates the point:

<ul id="menu">
 <li><a href="/">Home</a></li>
 <li><a href="/about/">About</a></li>
 <li><a href="/contact/">Contact</a></li>
</ul>

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

The list of links is plain HTML, so it exists for all users, whether or not they have scripting enabled. If scripting is supported, our menu.js script can apply dynamic behaviors, but if scripting isn't supported, the content still appears. We haven't differentiated between devices explicitly -- we've just provided content that's dynamic if the browser can handle it, and static if not.

Discussion

The "traditional" approach to this scenario would be to generate a separate, dynamic menu in pure JavaScript, and to have fallback static content inside a noscript element:

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

<noscript>
 <ul>
   <li><a href="/">Home</a></li>
   <li><a href="/about/">About</a></li>
   <li><a href="/contact/">Contact</a></li>
 </ul>
</noscript>

But, as we've already seen, a wide range of devices will fall though this net, because JavaScript support is no longer an all-or-nothing proposition. The above approach provides default content to all devices, and applies scripted functionality only if it works.

This scripting approach is popularly referred to as progressive enhancement, and it's a methodology we'll be using throughout this book.

Don't Ask!

Neither this technique nor the noscript element should be used to add a message that reads, "Please turn on JavaScript to continue." At best, such a message is presumptuous ("Why should I?"); at worst it may be unhelpful ("I can't!") or meaningless ("What's JavaScript?"). Just like those splash pages that say, "Please upgrade your browser," these messages are as useful to the average web user as a road sign that reads, "Please use a different car."

Occasionally, you may be faced with a situation in which equivalent functionality simply cannot be provided without JavaScript. In such cases, I think it's okay to have a static message that informs the user of this incompatibility (in nontechnical terms, of course). But, for the most part, try to avoid providing this kind of message unless it's literally the only way.

Separating Content from Behavior (Unobtrusive Scripting)

Separating content from behavior means keeping different aspects of a web page's construction apart. Jeffrey Zeldman famously refers to this as the "three-legged stool" of web development (Zeldman, J. Designing with Web Standards. New Riders, 2003) -- comprising content (HTML), presentation (CSS), and behavior (JavaScript) -- which emphasizes not just the difference in each aspect's functioning, but also the fact that they should be separated from one another.

Good separation makes for sites that are easier to maintain, are more accessible, and degrade well in older or lower-spec browsers.

Solution

At one extreme, which is directly opposed to the ideal of separating content from behavior, we can write inline code directly inside attribute event handlers. This is very messy, and generally should be avoided:

<div id="content"
   onmouseover="this.style.borderColor='red'"
   onmouseout="this.style.borderColor='black'">

We can improve the situation by taking the code that does the work and abstracting it into a function:

<div id="content"
   onmouseover="changeBorder('red')"
   onmouseout="changeBorder('black')">

Defining a function to do the work for us lets us provide most of our code in a separate JavaScript file:

Example 1.1. separate-content-behaviors.js (excerpt)

function changeBorder(element, to)
{
 element.style.borderColor = to;
}

But a much better approach is to avoid using inline event handlers completely. Instead, we can make use of the Document Object Model (DOM) to bind the event handlers to elements in the HTML document. The DOM is a standard programming interface by which languages like JavaScript can access the contents of HTML documents, removing the need for any JavaScript code to appear in the HTML document itself. In this example, our HTML code would look like the following:

<div id="content">

Here's the scripting we'd use:

Example 1.2. separate-content-behaviors.js

function changeBorder(element, to)
{
 element.style.borderColor = to;
}

var contentDiv = document.getElementById('content');

contentDiv.onmouseover = function()
{
 changeBorder('red');
};

contentDiv.onmouseout = function()
{
 changeBorder('black');
};

This approach allows us to add, remove, or change event handlers without having to edit the HTML, and since the document itself does not rely on or refer to the scripting at all, browsers that don't understand JavaScript will not be affected by it. This solution also provides the benefits of reusability, because we can bind the same functions to other elements as needed, without having to edit the HTML.

This solution hinges on our ability to access elements through the DOM, which we'll cover in depth in Chapter 5, Navigating the Document Object Model.

The Benefits of Separation

By practicing good separation of content and behavior, we gain not only a practical benefit in terms of smoother degradation, but also the advantage of thinking in terms of separation. Since we've separated the HTML and JavaScript, instead of combining them, when we look at the HTML we're less likely to forget that its core function should be to describe the content of the page, independent of any scripting.

Andy Clarke refers to the web standards trifle, which is a useful analogy, A trifle looks the way a good web site should: when you look at the bowl, you can see all the separate layers that make up the dessert. The opposite of this might be a fruit cake: when you look at the cake, you can't tell what each different ingredient is. All you can see is a mass of cake.

Discussion

It's important to note that when you bind an event handler to an element like this, you can't do it until the element actually exists. If you put the preceding script in the head section of a page as it is, it would report errors and fail to work, because the content div has not been rendered at the point at which the script is processed.

The most direct solution is to put the code inside a load event handler. It will always be safe there because the load event doesn't fire until after the document has been fully rendered:

window.onload = function()
{
 var contentDiv = document.getElementById('content');

 ...
};

Or more clearly, with a bit more typing:

window.onload = init;

function init()
{
 var contentDiv = document.getElementById('content');

 ...
}

The problem with the load event handler is that only one script on a page can use it; if two or more scripts attempt to install load event handlers, each script will override the handler of the one that came before it. The solution to this problem is to respond to the load event in a more modern way; we'll look at this shortly, in the section called "Getting Multiple Scripts to Work on the Same Page".

Using Braces and Semicolons (Consistent Coding Practice)

In many JavaScript operations, braces and semicolons are optional, so is there any value to including them when they're not essential?

Solution

Although braces and semicolons are often optional, you should always include them. This makes code easier to read -- by others, and by yourself in future -- and helps you avoid problems as you reuse and reorganize the code in your scripts (which will often render an optional semicolon essential).

For example, this code is perfectly valid:

Example 1.3. semicolons-braces.js (excerpt)

if (something) alert('something')
else alert('nothing')

This code is valid thanks to a process in the JavaScript interpreter called semicolon insertion. Whenever the interpreter finds two code fragments that are separated by one or more line breaks, and those fragments wouldn't make sense if they were on a single line, the interpreter treats them as though a semicolon existed between them. By a similar mechanism, the braces that normally surround the code to be executed in if-else statements may be inferred from the syntax, even though they're not present. Think of this process as the interpreter adding the missing code elements for you.

Even though these code elements are not always necessary, it's easier to remember to use them when they are required, and easier to read the resulting code, if you do use them consistently.

Our example above would be better written like this:

Example 1.4. semicolons-braces.js (excerpt)

if (something) { alert('something'); }
else { alert('nothing'); }

This version represents the ultimate in code readability:

Example 1.5. semicolons-braces.js (excerpt)

if (something)
{
 alert('something');
}
else
{
 alert('nothing');
}

Using Function Literals

As you become experienced with the intricacies of the JavaScript language, it will become common for you to use function literals to create anonymous functions as needed, and assign them to JavaScript variables and object properties. In this context, the function definition should be followed by a semicolon, which terminates the variable assignment:

var saySomething = function(message)
{
 ...
};

Adding a Script to a Page

Before a script can begin doing exciting things, you have to load it into a web page. There are two techniques for doing this, one of which is distinctly better than the other.

Solution

The first and most direct technique is to write code directly inside a script element, as we've seen before:

<script type="text/javascript">
function saySomething(message)
{
 alert(message);
}

saySomething('Hello world!');
</script>

The problem with this method is that in legacy and text-only browsers -- those that don't support the script element at all -- the contents may be rendered as literal text.

A better alternative, which avoids this problem, is always to put the script in an external JavaScript file. Here's what that looks like:

<script type="text/javascript" src="what-is-javascript.js"
   ></script>

This loads an external JavaScript file named what-is-javascript.js. The file should contain the code that you would otherwise put inside the script element, like this:

Example 1.6. what-is-javascript.js

function saySomething(message)
{
 alert(message);
}

saySomething('Hello world!');

When you use this method, browsers that don't understand the script element will ignore it and render no contents (since the element is empty), but browsers that do understand it will load and process the script. This helps to keep scripting and content separate, and is far more easily maintained -- you can use the same script on multiple pages without having to maintain copies of the code in multiple documents.

Discussion

You may question the recommendation of not using code directly inside the script element. "No problem," you might say. "I'll just put HTML comments around it." Well, I'd have to disagree with that: using HTML comments to "hide" code is a very bad habit that we should avoid falling into.

Putting HTML Comments Around Code

A validating parser is not required to read comments, much less to process them. The fact that commented JavaScript works at all is an anachronism -- a throwback to an old, outdated practice that makes an assumption about the document that might not be true: it assumes that the page is served to a non-validating parser.

All the examples in this book are provided in HTML (as opposed to XHTML), so this assumption is reasonable, but if you're working with XHTML (correctly served with a MIME type of application/xhtml+xml), the comments in your code may be discarded by a validating XML parser before the document is processed by the browser, in which case commented scripts will no longer work at all. For the sake of ensuring forwards compatibility (and the associated benefits to your own coding habits as much as to individual projects), I strongly recommend that you avoid putting comments around code in this way. Your JavaScript should always be housed in external JavaScript files.

The language Attribute

The language attribute is no longer necessary. In the days when Netscape 4 and its contemporaries were the dominant browsers, the <script> tag's language attribute had the role of sniffing for up-level support (for example, by specifying javascript1.3), and impacted on small aspects of the way the script interpreter worked.

But specifying a version of JavaScript is pretty meaningless now that JavaScript is ECMAScript, and the language attribute has been deprecated in favor of the type attribute. This attribute specifies the MIME type of included files, such as scripts and style sheets, and is the only one you need to use:

<script type="text/javascript">

Technically, the value should be text/ecmascript, but Internet Explorer doesn't understand that. Personally, I'd be happier if it did, simply because javascript is (ironically) a word I have great difficulty typing -- I've lost count of the number of times a script failure occurred because I'd typed type="text/javsacript".