![]() |
Web Development »
Client side scripting »
General
Advanced
MiniRacer - Extending W3C DOM Elements using JavaScript (IE)By Paul BakerA JavaScript table-based driving game to demonstrate how to extend W3C DOM Elements to add your own functionality. |
Javascript, Windows, Visual Studio, IE 6.0, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||

The code behind this short JavaScript game is aimed at the intermediate level and is intended to demonstrate the useful techniques of:
HTMLElements (table, table row, and table cell elements on this occasion)
HTMLElement, and which therefore can be added directly to our web-page whilst still exhibiting our added functionality. As such, this game is aimed at Internet Explorer 6 (& compatible) browsers. For simplicity (and an easy life), I have not stepped into the muddy waters of browser compatibility, but one big reason for supporting the W3C DOM is the hope that in time code across browsers will become increasingly standardized.
This article starts with an introduction to the game and the general principles of inheritance (prototyping in JavaScript). For the specific details regarding extending HTMLElements, take a jump down towards the end of this article.
I should make clear that whilst, hopefully, this game may be a good example of a few useful techniques, they are not techniques of my own invention. Some are well known; others less so. The References section at the bottom of this article directs the reader's attention to the four sources which were the most useful to me in writing this game.
The game is a simple driving game that I first met on my Amstrad CPC464, quite a few years ago. It utilizes a table to represent the race-track, with table cells making up the squares of the race-track. Four buttons allow you to steer the car in the four possible directions. The aim is to drive to the finish (indicated in red) without crashing into the walls.
You can also design a new track to race on, change the design of the current track, and even save a designed track by adding the URL to your Favorites (the track is stored in the querystring).
You can play the game online, or download the ZIP file which contains all of the code.
It would be possible, of course, to program games like this using a number of global functions that test various conditions and write HTML to the document using document.write and modifications to the innerHTML property of elements.
However, the benefits of a more object-oriented approach are substantial. In particular:
Because JavaScript does not force any kind of OO structure upon your code and, in addition, is a loosely typed language, an application of sheer discipline on your part is required to get started on this technique.
The basic principles shown below need some careful study to master, but, once mastered, offer big rewards. Although it may look complicated at first, it is also easy to step back and admire the basic simplicity of an object-oriented solution. The UML diagram for this solution is shown below.

The advantages of 'inheriting' from W3C DOM elements are two-fold:
insertRow method of the HTMLTableElement handles all of the pain in adding a new empty row to our table at any index position.
HTMLElement, it can be added to the web-page using the appendChild or replaceChild methods of any other HTMLElement. We can implement this approach using the following general principles:
RaceTrack - to represent the track on which the game takes place.
RaceRow - to represent a row of cells on the racetrack.
RaceCell - to represent an individual cell on the racetrack.
Car - to handle the current position of the car, and to deal with move updates, steering, and checking for crashes and finish points.
QueryStringParser - to parse the querystring passed to the HTML pages.
SpeedSelector - a wrap around the select element to set the car's speed. RaceTrack. function RaceTrack()
{
this.init();
}
this.init();
The init function does all the set-up for the object which usually is simply a matter of initializing member variables, e.g., this._rows = 0;.
[There is a reason for calling an init function rather than putting the init code into the constructor itself. Without going into detail, it is to ensure that if you later sub-class this function, then each object instance of the sub-class can still obtain its own member variables as opposed to sharing the variables between all instances of the function.]
RaceTrack.prototype.init = RaceTrack_init;
RaceTrack.prototype.clearTrack = RaceTrack_clearTrack;
Notice that there are no parameter brackets in any of these assignments!!
function RaceTrack_init()
{
this._rows = 0;
this._cols = 0;
}
function RaceTrack_clearTrack()
{
�
}
�As an example, miniRacer.js contains the following global functions and variables:
var tbl; // stores the race track table
var car; // stores as instance of the Car class
// The default track to race on
var DefaultRaceTrack = "wwwwwwwwwwwwrrr...";
var DefaultRowLength = 11;
function setup()
{
// Load racetrack in from querystring,
// using default track if not available
var qs = new QuerystringParser();
var track = qs.getRaceTrack(DefaultRaceTrack);
var rowLength = qs.getRowLength(DefaultRowLength);
tbl = document.createElement("RaceTrack");
tbl.load(track, rowLength, DISABLE_CELL_CLICK_HANDLER);
// add racetrack to webpage
var RaceTrackContainer = document.getElementById("RaceTrackContainer");
RaceTrackContainer.replaceChild(tbl, RaceTrackContainer.firstChild);
// create a new instance of our racecar
car = new Car(tbl, "car", new SpeedSelector("speedSelect"));
}
function designTrack()
{
// called when "Design This Track" link
// is clicked...redirects to designTrack.htm
car.reset();
window.location = "designTrack.htm?track=" +
tbl.serialise() + "&rowLength=" + tbl.getRowLength();
}
Notice how little code there is in the global functions. In an ideal situation, these do little more than creating the needed instances for the classes and then hooking them up as needed. Essentially, all of the code has been delegated into the most appropriate method of the most appropriate class. Each method can then be fairly easily unit tested when things go wrong, making for much easier testing and debugging.
var ENABLE_CELL_CLICK_HANDLER = true;
var DISABLE_CELL_CLICK_HANDLER = false;
var IGNORE_QUERYSTRING = true;
[Alternatively, you can attach the constants to their closest connected functions but I haven't gone that far.]
document.createElement method provided by the W3C DOM. The need for this and an explanation is given below. Here is the small catch. Usually, to make use of JavaScript's function-based equivalent to inheritance, we simply set the prototype object of a function to be the function that we wish to inherit from:
Dog.prototype = new Animal();
However, in Internet Explorer, DOM elements are not implemented using full JavaScript functions. This leads to error messages when using the prototype technique.
All is not lost though. Instead, we do two things in DOM_override.js:
document.createElement function with our own implementation. // store a reference to the original document.createElement
var __IEcreateElement = document.createElement;
// and now re-define the original
document.createElement = function (tagName) {
if(tagName=='RaceTrack')
{
return document.applyInherit(__IEcreateElement("table"), new RaceTrack());
} else
{
return __IEcreateElement(tagName);
}
}
Our replacement function usually delegates to the original. However, if it spots one of our class names (RaceTrack), it calls applyInherit, passing in a new instance of the correct HTMLElement and a new instance of our class (RaceTrack).
applyInherit do? Well, we define this below: document.applyInherit = function(original, interface)
{
for (method in interface)
original[method] = interface[method];
return original;
}It simply makes use of the fact that all properties and methods of a JavaScript function are available through the index notation [].
We iterate through all the properties and methods of our class, copying them to the instance of the HTMLElement. We then return the instance of the HTMLElement which is now equipped with all the properties and methods of our class. This achieves the inheritance, albeit in a rather unorthodox and manual way.
We can now create instances of our classes. We create an instance of our top level class RaceTrack using, e.g.:
var tbl = document.createElement("RaceTrack");
To enable the creation of instances of RaceRow and RaceCell, I add factory methods to RaceTrack and RaceRow respectively:
function RaceTrack_insertRaceRow()
{
return document.applyInherit(this.insertRow(), new RaceRow());
}
function RaceRow_insertRaceCell()
{
return document.applyInherit(this.insertCell(), new RaceCell());
}
this.insertRow and this.insertCell are defined for us within HTMLTableElement and HTMLRowElement respectively.
These neat functions now enable us to add the RaceCells to the track, using the following example code:
for(var i=0;i<rows;i++)
{
var row = this.insertRaceRow();
for(var j=0; j<cols;j++)
{
var cell = row.insertRaceCell();
cell.setX(j);
cell.setY(i);
}
}
The instance of RaceTrack (referenced below by tbl) can be plugged into the web-page using:
var RaceTrackContainer = document.getElementById("RaceTrackContainer");
RaceTrackContainer.replaceChild(tbl, RaceTrackContainer.firstChild);
For this to work, we need an element in the web-page with an id of "RaceTrackContainer".
<td id="RaceTrackContainer">
Racetrack goes here
</td>
I hope this article has been of some help to you. Some references are given below, all of which have been helpful in constructing this article.
This web-page covers extending the DOM elements with IE and JavaScript. It also shows a neat trick for adding functionality using Behaviors. The technique shown here is basically the idea used in this game.
A clever book covering an OOP approach to JavaScript. I've used some of the ideas from this book in this game.
Using a QueryString object to parse the QueryString. My simple QuerystringParser class comes basically from here with some negligible modifications.
An excellent, general JavaScript Reference. This book also includes a useful reference to the W3C DOM.
| You must Sign In to use this message board. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 16 Feb 2005 Editor: Smitha Vijayan |
Copyright 2005 by Paul Baker Everything else Copyright © CodeProject, 1999-2009 Web10 | Advertise on the Code Project |