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 t
oImperial() 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

[ About Melbourne PC User Group ]