Home » Guides Beginner Article

How to Write Web 2.0 JavaScript

2.9/5.0 (7 votes total)
Rate:

Adam Bergmark
June 09, 2006


Adam Bergmark
Adam Bergmark is a freelance web developer , focusing on accessible and unobtrosive JavaScript.


http://www.edea.se
Adam Bergmark has written 1 articles for JavaScriptSearch.
View all articles by Adam Bergmark...

Abstract

Would you like to learn how to write unobtrusive, modern, compact JavaScript that you can add to your HTML document without modifying the html itself at all (except for script tags in HEAD)? Then this article might be just what you are looking for. This is part 1 of (hopefully) 3 articles that I will write about this subject.

Support

Here's a short list of the most popular browsers and their JS implementations; IE, FF, OP and Webkit (SF)

EcmaScript 3
All current implementations derive from ES. Finalized December 1999
IE 5.5
JScript 5.5 (close to JavaScript 1.5), released July 2000.
IE 6.0
JScript 5.6 (close to JavaScript 1.5), released October 2001.
Firefox 1.0
JavaScript 1.5 Implemented
Mozilla 1.8/Firefox 1.5
JavaScript 1.6 implemented
Opera 7+
Javascript 1.5 implemented
Safari 1.0
Implements JavaScript 1.5?
Safari 2.0
Implements JavaScript 1.5?

This probably covers 99% of the visitors of your site. I did not mention IE 5.0/win or IE 5.2/mac, this is because they support JS poorly, and their usage is low and getting lower.

As you can see the JavaScript support is close to excellent, with a few oddities to watch out for here and there, such as IE not returning empty matches when using String:split, so, you will seldom have to worry about it.

Unfortunately the DOM support is not as high, and there are more pitfalls there. More on this later.

Semantic Markup

There is a lot of talk about semantic markup nowadays. One thing you will hear over and over again is keep markup and behaviour separate! What this means in practice is that you should put all JS code in a separate document, and load it through the src attribute of the script element; <script type="text/javascript" src="myscript.js"></script>. And this doesn't only include your main function declarations et cetera, it includes everything (every everything) that is JavaScript related. The main concern here is how events are added.

Event Basics

An event is a trigger, which may be executed when the user performs an action on either the document or the browser window, such as clicking a button. Since the event in this case will trigger on the click of the user, the event is called onclick.

No Camels, Please

A lot of times you will find code that camelcases the event names. Camelcasing is when you write a name without using whitespace or underscores, but capitalizing each new word, such as myFunctionName. Per definition all XHTML (and preferably also all HTML) tags and attributes are written in all lowercase. An example of a camelized event is onClick. Using onClick will work fine when assigning the event ">inline in your HTML code, but will break when we try to separate markup and behaviour.

Automated Events

There are a few events that does not regard the client's actions, such as <body onload="doStuff();"> which triggers as soon as all data in the current document has been parsed.

Important events include (but is not limited to): onload, onmousemove, onmouseover, onmousedown, onclick, onmouseup, onkeydown, onkeypress, onkeyup, onfocus, onblur, onchange, onunload (as we'll see later when we deal with IE's memory leak). There are a lot more, but most are either not standardized, obtrusive or poorly supported. A few seem pretty useless as well, such as IE's onbeforeunload which triggers before onunload. I fail to see how this event is useful.

Functions

Okay, if you don't know what a function is, you should read about it elsewhere, shoo shoo. If you have been using functions in other languages there might be a few gotchas that I will try to cover.

What really does the function name() {} do? In short, it creates a new Function object. You heard it, it's a Function object which behaves much like other OOP objects. It has a constructor which obviously is Function, it can also have it's own properties.

But let's hold that thought for a minute and look at this piece of code instead: function () {};. You will notices one main difference here; The name is missing! This is commonly known as an anonymous function (referred to as lambdas or procs in some other languages.) It does not have a name associated with it. But as with all javascript expressions, a value is returned, and the value returned from using this expression is the Function object itself. This means that we can store the function somewhere if we so wish: var fooAlerter = function () { alert("foo"); };. This is essentially the same operation as doing function fooAlerter() { alert("foo"); }. As for the semi-colon, when the function declaration is an expression as we can see here, it's usually considered good practise to always end with one. The following will break: var a = function () {} var b = function () {}

Even if you don't specify that a function returns a value, it will still return one, this value will be undefined.

A function may also be created - being the Objects they are - through the new Function() syntax. These three lines produce the same result:

function a()
{
alert("a");
}
var b = function () {
alert("b");
};
var c = new Function("alert('c')");

I strongly advise you to not use the new Function() syntax because it is bound to create escaping nightmares and preventing you from using syntax highlighting, similiar to eval(). But as with eval, there are instances where you will need it.

Creating a Reference to a Function

Without digging too deep into OOP i will try to explain how a reference works. Consider the following:

var a = 1;
var b = a;
b++;

It's pretty easy to figure out that b will equal 2 in this instance. a in its turn will still equal 1. This is because a and b both contain a reference to a primitive object. Primitive objects can not change, so when we do b++; a new primitive object holding the value 2 will be created, and b will be set to reference to that value instead. a on the other hand will still point to the same object as it did before and equal 1.

So, since the ES engine is somewhat optimized, it never creates two primitives that has the same value. var a = 1; var b = 1; will have the exact same result as the code above, and no relational issues between the variables will exist.

But regular objects can change, and will always change, unless you tell them otherwise (unfortunately there is no built-in method for cloning an object in JavaScript). To explain this, look at this code:

var randomArray = ["Apple", "Banana", "Mango", "Porsche"];
var fruits = randomArray;
/* Oh wait, "Porsche" is not really a fruit, is it? */
fruits.pop(); // remove the last item

fruits will contain what everybody expects; ["Apple", "Banana", "Mango"]. But what about randomArray? You probably figured it out. Since regular objects can change, it changed. No new objects were created and no references were replaced for new ones. so randomArray and fruits still point to the same object, but now both of them "contain" 3 items.

An interesting observation here is, that a variable can never really equal "baseball" or ["cat", "dog"]. A variable does not contain an object, be it primitive or regular. All the information a variable needs is a reference (or pointer) to an address in memory where an object is stored.

To all you C-programmers out there: Contrary to what many believe, JavaScript (and Java, and Ruby...) do have pointers. What they don't have is pointer arithmetic, and it's never really needed either.

A Function is indeed an object, therefore you can safely do the following:

var myFunc = function () {
alert("this is myFunc");
};
var myOtherFunc = myFunc;

All we did was copy the reference to the function contained within myFunc to myOtherFunc. If you think about it, it makes complete sense that this is possible. Now check out the following, which often confuses beginners because they lack the proper basics:

function myFunc()
{
alert("this is myFunc");
}
var myOtherFunc = myFunc;

As we saw earlier, the declarations of myFunc in these two examples produce the same result. Therefore myOtherFunc will successfully copy the reference to the function in both cases. Along the lines of the newbie, he might try to do var myOtherFunc = myFunc(); instead of what we did. Looking at this, however un-intuitive as it may be, you should be able to figure out that it doesn't really make sense. As soon as the line is parsed, myFunc() is executed! And the return value is assigned to myOtherFunc instead. Lo and behold the surprise of the script kiddie when his code essentially is "bananas!"(); and he gets the error message string is not a function. But fortunately for you, you are way above the script kiddie's level. Feel free to make fun of him (a little...)

Looking Back

So, why in the world have i been ranting about functions? First of all, a common case is that people seem to assume that since a function doesn't work like this in say, PHP, it will be the same for other languages. The second reason is that now you might be able to understand the rest of this article better, and you won't have to look through other code just to figure out what the heck you're doing.

DOM Basics

Quick walkthrough

The DOM is a tree-like structure representing the current document. An element is a node, and a node can have parents (the parent of <body> is <html>) and logically then, also children (this makes <html> the parent of <body>). All elements have parents, except for html, which is the root node. Surely this doesn't come as a surprise even if you only know very little HTML and XML. There are a total of twelve types of nodes. A textNode is one of them; unsurprisingly, it holds text. A textNode can not have children. Not being aware of textNodes is a common cause of computers thrown out the windows, and Windows thrown out of computers. The misconception is that an element, such as P, can contain text. This is not true however, the P-element has to contain a textNode, which in its turn contains text.

Traversing and Fetching Information

There are several ways to get to the information you want in the DOM, and this is probably where the most outdated techniques can be found haunting the old trustworthy WWW.

Retrieve Elements

W3C has standardized a few ways of retrieving elements:

  • Use document.getElementById("id") to fetch a single element with the specified ID attribute (remember that two elements are never supposed to have the same ID and be on the same page.)
  • Use document.getElementsByTagName("tagName") to fetch all elements with the specified tag name, you also do anyTag.getElementsByTagName("tagName") to fetch children and descendants of that specific tag. Another feat is x.getElementsByTagName("*") which fetches all descendants, regardless of their tag name. Do note that * only works if used by itself, using "t*" will NOT fetch both TD and TABLE elements.
  • Use myTag.childNodes[index] to retrieve the node with the specific index seen from its parent. One way of looping through an elements childNodes is by using myTag.childNodes.length. The first and last childNode can also be accessed by using obj.firstChild and obj.lastChild, respectively.
  • Certain elements have their own accessor properties that are pre-cached by the browser. These include document.forms, document.images, document.links. Most of the time these are incorrectly used along with name attributes, such as getting <form name="sendFlowers"> by using document.forms.sendFlowers. This is well supported, but it violates the DTDs that say that forms/images/links aren't supposed to have name attributes. Luckily for us, we can just exchange the name for an ID and use the same code: <form id="sendFlowers"> and document.forms.sendFlowers. In some web 2.0 groups using these properties are frowned upon because they are considered non-standard. They are defined though, but not in the regular DOM Core. Instead, see the HTML DOM Specification, where you easily will find them.

To all or dismay, there is no standardized way to fetch a collection of elements of your specified common denominator except by their position in the DOM hierarchy. The obvious choice would be to go the CSS route and allow fetching of elements with the same class. This has been done several times, Dean Edwards was probably first with CSSQuery, and it is also found in more renowned toolkits such as jQuery and Prototype 1.5, among others.

Potential Pitfalls

IE & OP: document.getElementById and the name attribute
using getElementById on an id where another element has that ID but as the name attribute, it might return the element with the name instead. Testcase: http://www.csb7.com/test/ie_getelementbyid_bug/index.php
document.getElementsByTagName/childNodes does not return an Array instance
The object returned by these methods is not an Array but an HTMLCollection (a NodeList when handling XML). It is array like in the sense that it has enumerated properties (meaning obj[0] is the first element and obj[1] the second etc) and also a length property. But the similarities end there. So don't try to pop() out the values or use other Array prototype methods.
Gecko: Parsing of whitespace
Mozilla/Firefox parses all whitespace into textNodes. Other browsers don't. This could be painful and there is no perfect solution to the problem. Solution #1 is to add an event that removes all white-space when the document loads. Solution #2 would be to skip these nodes when iterating. I'm not particularly fond of either of these operations and tend to stay away from using childNodes if possible.

Retrieve Attribute Values

Attributes can be fetched in two ways, either by simply using element.attributeName or by using the longer element.getAttribute("attributeName"). Using the latter will cause you complications when fetching the class attribute of elements. Since class is a reserved word in JS you have to use obj.className to fetch the class(es) of an object using the shorter notation. getAttribute on the other hand requires you to pass "className" for IE and "class" for the other browsers to get this result. You could use get/setAttribute to get and set events, but it's as ugly as using new Function()

Retrieving the Value of textNodes

Quite simple, and cross-browser (never thought you'd hear that, am I right?): myTextNode.nodeValue. This means that if you have the following HTML <p>pancakes!</p> you will use p.firstChild.nodeValue to get "pancakes!".

Rules of Thumb

If you find yourself hunting for scripts, you should quickly close the browser tab and wash your hands if you see any of the following properties used:

  • document.all
  • document.layers
  • document.formName.formElement
  • document.forms.formName is border-line (note that document.forms.formID is the proper way to use document.forms)

Conclusion

You should by now have a pretty firm grip of the basics necessary to create events. But there are still topics that need to be covered along with quirks and work-arounds that you need to be aware of. So grab the RSS feed if you want to know as soon as new parts are added.

Until a comment system is created, you can always contact me as Raevel on IRC, or by e-mail at adam [at] edea.se

Digg this article

This article was last modified 2006-06-09 14:47 +0100


Add commentAdd comment (Comments: 0)  

Advertisement

Partners

Related Resources

Other Resources

arrow