The magazine of the Melbourne PC User Group
JavaScript — Which Concept Next?
Trevor Gosbell |
|
|
Trevor Gosbell takes us into the next stage of development by introducing the
concepts of Object Oriented Programming
See previous articles in PC Update for
June,
July,
August and
September |
If we're talking about real programming in JavaScript then we have to go
object-oriented. These days it seems that a programming language can't be
considered more than a toy unless it is object-oriented.
So I'm sure you'll be relieved and pleasantly surprised to learn that JavaScript
holds its own as an object-oriented language.
But first, what are objects and why should our programming be oriented towards
them?
There isn't anything special about objects - they are just a collection of data
values (or "properties") and functions (or "methods").
A powerful application of objects is to use them as user-defined data types,
which is what we will do in this article.
Choice of the word "object" is a bit unfortunate because in daily use, we use
"object" to refer to things that have a physical presence. While programming
objects can represent real things, they are often used to represent more
abstract concepts as we will see shortly.
The Specification
|
The specification for our object-oriented program is simple: Produce a program
that converts measurements between imperial and metric units. The program takes
a number as input, and outputs the converted value. The program should be able
to convert standard units of length, weight, area, volume, and temperature. The
user interface might look like Figure 1. |

Figure 1 |
First Attempt - No Objects
We'll start by making our first attempt without objects. This is not usual
practice; object-oriented programmers start their planning with objects but we
will start in familiar territory and then orient ourselves to the objects.
Listing 1
<html>
<head>
<title>JavaScript Example 01</title>
<script language="JavaScript">
<!--
function toImperial( value )
{
return( value / 2.54 + " inches");
}
function toMetric( value )
{
return( value * 2.54 + " cm" );
}
//-->
</script>
</head>
<body>
<form name="ioForm">
<label>Value: </label>
<input type="text" name="inputField" /><br />
<label>Converted:</label>
<input type="text" name="outputField" /><br />
<input type="button"
value="cm to inches"
onClick="outputField.value = toImperial
(inputField.value );" />
<input type="button"
value="inches to cm"
onClick="outputField.value = toMetric
(inputField.value );" />
</form>
</body>
</html> |
Take a close look at Listing 1. I'll start with discussion of the <body>
section.
<form name="ioForm">
...
</form>
You may recognise this as the same input/output form that I introduced in
September, with a few changes:
<label>Value: </label>
<input type="text" name="inputField" /><br />
<label>Converted:</label>
<input type="text" name="outputField" /><br />
Input and output text fields, nothing much new there.
<input type="button"
value="cm to inches"
onClick="outputField.value = toImperial( inputField.value );" />
<input type="button"
value="inches to cm"
onClick="outputField.value = toMetric( inputField.value );" />
Two almost identical buttons. Remember that for a button, the value attribute
holds the text that appears on the face of the button, so you can see that these
buttons are intended to perform the reverse conversion of the other.
The
onClick
attribute holds a snippet of JavaScript code that is executed when
the user clicks the button. The code is almost identical in both cases, so we'll
look at the first button in more detail:
outputField.value = toImperial( inputField.value );
On the left of the equals sign we can see that a new value is being assigned to
the
outputField. On the right of the equals sign is a call to the function
toImperial(), with the value held in the
inputField
being passed to the
function. This means that whatever value is returned by
toImperial
will be the
new value displayed in the
outputField.
The same applies to the second button, except that the
toMetric()
function is
called instead.
Now we'd better have a look at the <head> section to find out about these two
new functions:
function toImperial( value )
{
return( value / 2.54 + " inches");
}
function toMetric( value )
{
return( value * 2.54 + " cm" );
}
Both pretty simple really - take the value that has been passed, multiply or
divide by the conversion factor, tack-on the new unit of measurement, and return
the result. Figure 2 shows a sample output.
Will It Scale? |

Figure 2. |
I hope it's pretty obvious that this won't "scale" very well. That is, every
time I add a new set of conversion buttons I would need to add two new
functions. For example, if I added a "km to miles" button and a "miles to km"
button I would also need to add
kmToMiles()
and
milesToKm()
functions. This is
particularly unwieldy because the
kmToMiles()
function would provide essentially
the same operation as
toImperial()
- only the conversion factor and the unit of
measurement would change.
Second Attempt
So I'm going to bring in an object to help out here. What we need is a
"converter" object - see listing 2.
Listing 2
<html>
<head>
<title>JavaScript Example 02</title>
<script language="JavaScript">
<!--
function Converter( factor, metric, imperial )
{
this.conversionFactor = factor;
this.metricUnit = metric;
this.imperialUnit = imperial;
this.toMetric = toMetric;
this.toImperial = toImperial;
}
var lengthConverter = new Converter( 2.54, " cm", " inches" );
function toMetric( value )
{
return( value * this.conversionFactor + this.metricUnit );
}
function toImperial( value )
{
return( value / this.conversionFactor + this.imperialUnit );
}
function makeButtons()
{
buttonCode = '<input type="button" value="';
buttonCode += lengthConverter.metricUnit + ' to';
buttonCode += lengthConverter.imperialUnit
buttonCode += '" onClick="outputField.value = ';
buttonCode += 'lengthConverter.toImperial( inputField.value );" /> ';
document.write( buttonCode );
buttonCode = '<input type="button" value="';
buttonCode += lengthConverter.imperialUnit + ' to';
buttonCode += lengthConverter.metricUnit
buttonCode += '" onClick="outputField.value = ';
buttonCode += 'lengthConverter.toMetric( inputField.value );" /><br />';
document.write( buttonCode );
}
//-->
</script>
</head>
<body>
<form name="ioForm">
<label>Value: </label>
<input type="text" name="inputField" /><br />
<label>Converted: </label><input type="text" name="outputField" /><br />
<script language="JavaScript">
<!--
makeButtons();
//-->
</script>
</form>
</body>
</html> |
We'll start at the top this time.
function Converter( factor, metric, imperial )
{
this.conversionFactor = factor;
this.metricUnit = metric;
this.imperialUnit = imperial;
this.toMetric = toMetric;
this.toImperial = toImperial;
}
This function, known as a constructor, is all that's required to make a
Converter object. It is not the object itself rather it is a template that
allows any number of Converter objects to be created.
When this constructor is called, it needs to get three parameters, factor,
metric, and imperial - the conversion factor, the metric unit of measurement,
and the imperial unit. These values are stored in three properties:
this.conversionFactor, this.metricUnit,
and
this.imperialUnit.
In the constructor, the this keyword is shorthand for "this object". Let's see
how this works. The next line shows the constructor in use:
var lengthConverter = new Converter( 2.54, " cm", " inches" );
At first glance this looks just like an ordinary function call but the key
difference is the presence of the new keyword, which is the instruction for
creating a new object. In this case the object is constructed based on
Converter(), and the three parameters are passed in the same way as they would
be to any other function. The end result is that the variable
lengthConverter
holds a Converter object, with the following properties:
lengthConverter.conversionFactor = 2.54
lengthConverter.metricUnit = " cm"
lengthConverter.imperialUnit = " inches"
Returning to the last few lines of the contructor:
this.toMetric = toMetric;
this.toImperial = toImperial;
These two properties add the methods toMetric() and toImperial() to the object.
They refer to these two functions:
function toMetric( value )
{
return( value * this.conversionFactor + this.metricUnit );
}
function toImperial( value )
{
return( value / this.conversionFactor + this.imperialUnit );
}
Very similar to our original
toImperial()
and
toMetric()
functions, you can see
that these have been generalised: the conversion factor and the appropriate unit
of measurement are taken from the object. Although these functions are described
outside the constructor, they are only intended to be called as methods of
Converter-type objects.
So the lengthConverter object also has two methods:
lengthConverter.toMetric()
lengthConverter.toImperial()
An Aside - Writing HTML Code
You can find another major change from our first version down in the
<body>
section. In Listing 1 we had the HTML code for two buttons, in Listing 2 there
is:
<script language="JavaScript">
<!--
makeButtons();
//-->
</script>
a call to the JavaScript function
makeButtons(). The code for this function is
found back in the head section and it comes in two parts, one for each button.
function makeButtons()
{
buttonCode = '<input type="button" value="';
buttonCode += lengthConverter.metricUnit + ' to';
buttonCode += lengthConverter.imperialUnit
buttonCode += '" onClick="outputField.value = ';
buttonCode += 'lengthConverter.toImperial( inputField.value );" /> ';
document.write( buttonCode );
The aim here is to produce a value in
buttonCode
equal to
<input type="button"
value=" cm to inches"
onClick="outputField.value = lengthConverter.toImperial(
inputField.value );" />
This is then inserted into the HTML document using the
document.write()
method.
Note that the metric and imperial units are obtained from the object properties
using "dot notation":
lengthConverter.metricUnit
lengthConverter.imperialUnit
And the toImperial()
method is also used in the same way:
lengthConverter.toImperial()
Dot notation is a standard way of representing object hierarchies in many
programming languages.
The second button is created in the same way:
buttonCode = '<input type="button" value="';
buttonCode += lengthConverter.imperialUnit + ' to';
buttonCode += lengthConverter.metricUnit
buttonCode += '" onClick="outputField.value = ';
buttonCode += 'lengthConverter.toMetric( inputField.value );" /><br />';
document.write( buttonCode );
}
Note that these tags are inserted into the document at the point where the
makeButtons()
method is called, not where the body of the function is located.
The result is two buttons immediately below the input and output fields.
Scaling It Up
The result of Listing 2 produces output identical to Listing 1, so have we
actually achieved anything? Of course! It is now very easy to add extra
converters, as shown in Listing 3.
Listing 3
<html>
<head>
<title>JavaScript Example 03</title>
<script language="JavaScript">
<!--
function Converter( factor, metric, imperial )
{
this.conversionFactor = factor;
this.metricUnit = metric;
this.imperialUnit = imperial;
this.toMetric = new Function( "value", "return( value *
this.conversionFactor
+ this.metricUnit )" );
this.toImperial = new Function( "value", "return( value /
this.conversionFactor
+ this.imperialUnit )" );
}
var converterArray = new Array();
converterArray.push( new Converter( 2.56, " cm", " inches" ) );
converterArray.push( new Converter( 1.609347, " km", " miles" ) );
converterArray.push( new Converter( 0.3048, " m", " feet" ) );
converterArray.push( new Converter( 0.04047, " hectares", " acres" ) );
converterArray.push( new Converter( 4.546, " litres", " gallons" ) );
converterArray.push( new Converter( 0.4534924, " kg", " pounds" ) );
converterArray.push( new Converter( 1.8, " Celsius", " Farenheit" ) );
converterArray[6].toImperial = new Function( "value", "return ((value *
this.conversionFactor) + 32) + this.imperialUnit;" );
converterArray[6].toMetric = new Function( "value", "return (( value -
32)/
this.conversionFactor) + this.metricUnit;" );
function makeButtons()
{
for ( var index = 0; index < converterArray.length; index++ )
{
buttonCode = '<input type="button" value="';
buttonCode += converterArray[index].metricUnit + ' to';
buttonCode += converterArray[index].imperialUnit
buttonCode += '" onClick="outputField.value = converterArray[';
buttonCode += index + '].toImperial( inputField.value );" /> ';
document.write( buttonCode );
buttonCode = '<input type="button" value="';
buttonCode += converterArray[index].imperialUnit + ' to';
buttonCode += converterArray[index].metricUnit
buttonCode += '" onClick="outputField.value = converterArray[';
buttonCode += index + '].toMetric( inputField.value );" /><br />';
document.write( buttonCode );
}
}
//-->
</script>
</head>
<body>
<form name="ioForm">
<label>Value: </label>
<input type="text" name="inputField" /><br />
<label>Converted: </label><input type="text" name="outputField" /><br />
<script language="JavaScript">
makeButtons();
</script>
</form>
</body>
</html> |
Note: There is a short
article in
PC Update for Jan / Feb 2005 with an some alternative calculations which
can be incorporated into the listing above.
There is a minor change to the Converter constructor function:
this.toMetric = new Function( "value", "return( value *
this.conversionFactor + this.metricUnit )" );
this.toImperial = new Function( "value", "return( value /
this.conversionFactor + this.imperialUnit )" );
Because the
toMetric()
and
toImperial()
functions in Listing 2 were simple one
line functions, I have included them directly into the constructor rather than
have them as separate functions.
The new Function instruction creates a new object of type Function. That's
right, functions are objects in JavaScript and
Function()
is their constructor,
which provides us with an alternative way to describe functions.
To create a function in this way, the first parameter passed to the
Function()
constructor is the parameter of the function that is being created and the
second parameter is the actual code body of the new function. This is a fairly
advanced idea, so don't be concerned if you don't get it at first.
An Array Of Objects
Next we create an array:
var converterArray = new Array();
Hold on a moment - if we're using new
Array()
that must mean that arrays are
objects too. It looks like there's objects everywhere in JavaScript and we've
been using them already.
Now that we have an array available, we can use it to hold a series of Converter
objects:
converterArray.push( new Converter( 2.56, " cm", " inches" ) );
converterArray.push( new Converter( 1.609347, " km", " miles" ) );
converterArray.push( new Converter( 0.3048, " m", " feet" ) );
converterArray.push( new Converter( 0.04047, " hectares", " acres" ) );
converterArray.push( new Converter( 4.546, " litres", " gallons" ) );
converterArray.push( new Converter( 0.4534924, " kg", " pounds" ) );
converterArray.push( new Converter( 1.8, " Celsius", " Fahrenheit" ) );
Note that the
push()
method is used to add an extra value to an array. Having
started with an empty array,
converterArray
has seven elements by the end of
this section of code.
There Is Always One
Temperature conversions are a problem because it's not simply a matter of
applying a conversion factor, there is also an offset of 32 degrees. This means
that the usual
toImperial()
and
toMetric()
methods won't work for temperature,
so we need to make a special case of that object:
converterArray[6].toImperial = new Function( "value", "return ((value
* this.conversionFactor) + 32) + this.imperialUnit;" );
converterArray[6].toMetric = new Function( "value", "return (( value -
32 )/this.conversionFactor) + this.metricUnit;" );
Using the object syntax it is possible to redefine the two methods for this
particular object.
A Stack Of Buttons
The array holding the Converter objects is really helpful when we come to adjust
the
makeButtons()
function. It is a simple matter to apply a for loop to the
array then create a pair of buttons for each object in the same way as in
Listing 2. One particularly handy thing to note is that dot notation works with
array elements, for example in this line:
buttonCode +=
converterArray[index].metricUnit
+ ' to';
Typical output from the code in Listing 3 can be seen in Figure 3. |

Figure 3. |
Wrap Up
People are often surprised to learn that JavaScript is an object-oriented
language. There are a couple of concepts from object-oriented programming that
we haven't covered here, such as encapsulation (which JavaScript doesn't do) and
inheritance (which JavaScript does do). But we have seen how objects can be used
to define new data types and how an almost endless series of objects can be
created from a constructor.
Reprinted from the November 2004 issue of PC Update, the magazine of Melbourne PC User Group, Australia
|