Articles: 843 | Categories: 148   
   
   
Home Articles Contact Us
 
 
 
 
Writing JavaScript Classes with Prototype (0 Comments)
Admin: Posted Date: April 4, 2010

While JavaScript is by design an object-oriented language, it hasn't always been the easiest language to create nicely structured code that is easily maintainable.

Writing JavaScript Classes with Prototype

Introduction

While JavaScript is by design an object-oriented language, it hasn't always been the easiest language to create nicely structured code that is easily maintainable. Prototype helps with this by providing a number of useful features for creating classes, such as the ability to extend classes and to easily group all functions together by using function binding. In this article I will show you how to create classes using JavaScript and Prototype.

To begin with, I'll show you how to define classes and how to create the class constructor. Next I will show you how to create child classes by extending your classes. After this I will introduce you to function binding and how it applies to the development of JavaScript classes.

Next I will show to create a string representation of instances of your created classes, then how to create custom enumerables (as mentioned in the third article in this series).

This article assumes you have read and understood the first five articles in this series, and also that you have at least a basic knowledge of how object-oriented programming (OOP) works.

Declaring Classes

To create a new class with Prototype, the Class.create() function is used. This method accepts an object as the first argument, with each element of that object corresponding to a class method. The created class is returned from the call to Class.create().

Listing 1 shows an example of creating a new class called Person. An instance of this class represents a single person. I will build on this particular class as we continue through this article.

This class declares one property (name), in which the person's name is held. It also declares two functions; one for setting the name (setName()) and another for retrieving the name (getName()).

Listing 1 Creating a basic JavaScript class (listing-1.html)
<html>
<head>
<title>Creating a Basic JavaScript Class</title>
<script type="text/javascript" src="/js/prototype.js"></script>
</head>
<body>
<script type="text/javascript">
var Person = Class.create({
name : null,
setName : function(name)
{
this.name = name;
},
getName : function()
{
return this.name;
}
});
var me = new Person();
me.setName('Quentin Zervaas');
alert(me.getName());
</script>
</body>
</html>

Declaring the name property is not required (you can just assign directly to the variable to create it), but doing so makes it clearer that the property exists.

One important facet of creating classes in this manner is you must remember that each function is an object element (the first argument to Class.create()), which therefore means you must separate each element by comma. Think of it as Class.create({ function1, function2, function3 }).

As you can see in Listing 1, once the class has been declared we instantiate it using the new keyword, just as you would with most other OOP languages.

Saving and Accessing Classes

My own personal preference when saving classes is to save each class to a separate file, using the filename ClassName.js.

Note: Additionally, when I want to namespace (that is, group classes into separate packages) I will name the class PackageName_ClassName and save the file to PackageName/ClassName.js.

Therefore, re-working the Person class into a separate file would result in the code in Listing 2. We can then load the class file and instantiate the class as shown in Listing 3.

Listing 2 Declaring the Person class in an external file (Person.js)
var Person = Class.create({
name : null,
setName : function(name)
{
this.name = name;
},
getName : function()
{
return this.name;
}
});
Listing 3 Loading and instantiating the Person class (listing-3.html)
<html>
<head>
<title>Loading and instantiating the Person class</title>
<script type="text/javascript" src="/js/prototype.js"></script>
<script type="text/javascript" src="/js/Person.js"></script>
</head>
<body>
<script type="text/javascript">
var me = new Person();
me.setName('Quentin Zervaas');
alert(me.getName());
</script>
</body>
</html>

You should always be declaring your classes in external files and not inline in your HTML. In fact, you should almost never include any JavaScript code in your HTML files.

Defining the Class Constructor

In Object-Oriented Programming, the class constructor is a method that is executed when the class is instantiated. By default JavaScript doesn't use constructors when you create classes, but Prototype allows us to do so by creating a function called initialize().

When you create a new instance of your class the initialize() function will be executed. You can pass arguments to the constructor by including them when you instantiate the class.

To demonstrate this, I'll now modify the Person class so rather than having to set the name by calling setName() after you create a Person object, you can set the name in the constructor.

Listing 4 shows the new version of the Person.js file, while Listing 5 shows how you can now instantiate the class and set the name without explicitly calling setName().

Listing 4 The Person class with a constructor which sets the name (Person.js)
var Person = Class.create({
name : null,
initialize : function(name)
{
this.setName(name);
},
setName : function(name)
{
this.name = name;
},
getName : function()
{
return this.name;
}
});
Listing 5 Defining the person's name when instantiating the Person class (listing-5.html)
<html>
<head>
<title>Loading and instantiating the Person class</title>
<script type="text/javascript" src="/js/prototype.js"></script>
<script type="text/javascript" src="/js/Person.js"></script>
</head>
<body>
<script type="text/javascript">
var me = new Person('Quentin Zervaas');
alert(me.getName());
</script>
</body>
</html>

Inheritance

A child class is one that extends from another class, meaning the properties and methods of the parent class now also belong to the child. Additionally, the child class can overwrite any methods as it sees fit, or it can create its own methods that don't exist in the parent.

While JavaScript doesn't provide OOP features such as abstract classes or interfaces, Prototype does make it fairly straightforward to create child classes.

Note: Technically it's possible to emulate abstract classes by creating a method in the abstract class that throws an exception. This exception will not occur if the child class has its own implementation of the method.

To create a child class, you still use the Class.create() method, but rather than passing the class methods as the first argument, you pass them as the second argument, and instead you pass the name of the parent class as the first argument. This is demonstrated in Listing 6.

According to the naming rules mentioned earlier in the article, my preference is to call this class Person_Australian (since it becomes part of the Person package) and to save it in a directory called Person (./Person/Australian.js).

Listing 6 Creating a sub-class of Person (Australian.js)
var Person_Australian = Class.create(Person, {
// all methods are inherited, more can be defined here
});

Because Prototype isn't really designed to have class inheritance, you must make the small concession of adding an extra parameter to each method you want to overwrite in child classes. This extra parameter is called $super and it is the first argument to the method. It is in fact a variable holding the parent method of the same name, meaning you can call $super().

Listings 7 and 8 demonstrate this by adding a method called getCountry() to the Person class. In the Person_Australian class we override this method so we can return the name of the country. As you can see, the method accepts $super as its first (and in this case, only) argument; however when you call this method you still don't use any arguments since Prototype internally passes the $super argument. This is demonstrated in Listing 9 which loads and instantiates the two classes.

Note: Since the declaration of the Person_Australian class relies on the Person class, you must be sure to load the Person.js file before Australian.js.
Listing 7 Declaring the parent getCountry() method (Person.js)
var Person = Class.create({
name : null,
initialize : function(name)
{
this.setName(name);
},
setName : function(name)
{
this.name = name;
},
getName : function()
{
return this.name;
},
getCountry : function()
{
return 'Unknown';
}
});
Listing 8 Overriding getCountry() in the child class (Australian.js)
var Person_Australian = Class.create(Person, {
getCountry : function($super)
{
return 'Australia, not ' + $super();
}
});
Listing 9 Loading and instantiating the Person and Person_Australian classes (listing-9.html)
<html>
<head>
<title>Loading and instantiating the Person and Person_Australian classes</title>
<script type="text/javascript" src="/js/prototype.js"></script>
<script type="text/javascript" src="/js/Person.js"></script>
<script type="text/javascript" src="/js/Person/Australian.js"></script>
</head>
<body>
<script type="text/javascript">
var you = new Person('Some Person');
alert(you.getCountry());
// displays "Unknown"
var me = new Person_Australian('Quentin Zervaas');
alert(me.getCountry());
// displays "Australia, not Unknown"
</script>
</body>
</html>

Function Binding

When using Prototype to develop classes, you need to have an understanding of what function binding is and how to use it. Essentially what binding does is instruct what the variable this refers to in a function. This is especially useful for event handling and for handling Ajax responses.

As I showed you in the fourth article of this series ("Event Handling in Prototype"), to observe an event on an element you use theElement.observe('eventName', handler). If you are developing a class then you want your handler function to be one of your class methods. This is primarily so you can access other class methods when handling the event.

Consider the code in Listing 10. In this example, when the user clicks the button the _onButtonClick function is called. Our aim is to display the string returned by the getMessage() method when the button is clicked.

Note: My personal preference is to name event handlers using an underscore since the method shouldn't directly be called. Typically this format is used to indicate protected or private methods.

This code will not yet work, as explained following the listing!

Listing 10 Demonstrating the drawback of not using function binding (listing-10.html)
<html>
<head>
<title>Demonstrating the drawback of not using function binding</title>
<script type="text/javascript" src="/js/prototype.js"></script>
</head>
<body>
<div>
<input type="button" value="Click Me!" id="myButton" />
</div>
<script type="text/javascript">
var MyClass = Class.create({
initialize : function(button)
{
button = $(button);
button.observe('click', this._onButtonClick);
},
getMessage : function()
{
return 'This is a simple function';
},
_onButtonClick : function(e)
{
var button = Event.element(e);
var message = this.getMessage();
button.up().update(message);
}
});
new MyClass('myButton');
</script>
</body>
</html>

The problem that occurs in this example is that when _onButtonClick runs, the getMessage() method is not found. This is because the keyword this doesn't refer to the instance of MyClass.

To solve this problem, Prototype provides two methods: bind() and bindAsEventListener(). They are basically the same thing, the difference being that you should use bindAsEventListener() specifically when you are observing events, because then Prototype knows to pass in the event object to the callback handler.

Listing 11 demonstrates usage of the bindAsEventListener(). This method accepts a single argument: the variable to bind the function to.

Listing 11 Binding a function with bindAsEventListener (listing-11.html)
<html>
<head>
<title>Binding variable with bindAsEventListener</title>
<script type="text/javascript" src="/js/prototype.js"></script>
</head>
<body>
<div>
<input type="button" value="Click Me!" id="myButton" />
</div>
<script type="text/javascript">
var MyClass = Class.create({
initialize : function(button)
{
button = $(button);
button.observe('click', this._onButtonClick.bindAsEventListener(this));
},
getMessage : function()
{
return 'This is a simple function';
},
_onButtonClick : function(e)
{
var button = Event.element(e);
var message = this.getMessage();
button.up().update(message);
}
});
new MyClass('myButton');
</script>
</body>
</html>

Now, inside the _onButtonClick() method, this refers to the instance of MyClass, meaning the getMessage() method can now be called.

Binding Functions for Ajax Requests

Although function binding will be most often used for event handling (using bindAsEventListener()), you will also need to bind functions when handling the response from Ajax requests. Since you don't need an event handler passed in (this concept doesn't apply to Ajax requests) we use bind() instead of bindAsEventListener(), once again accepting the variable to which the function should be bound.

In the previous article in this series I demonstrated a simple example of retrieving JSON data from the server and updating an element on the page with the returned data. I will now rewrite this same example using a class and function binding.

Listing 12 shows the PHP code used to return JSON data from the server. This is the script that will be accessed in the Ajax request. For a further explanation of how this all works, please refer to the previous article.

Listing 12 Sending JSON data from the server (listing-12.php)
<?php
$person = array(
'name'    => 'Quentin',
'country' => 'Australia'
);
header('Content-type: application/json');
echo json_encode($person);
?>

Now comes the interesting part. In Listing 13 we define a class called MyClass which accepts a single argument to the constructor. This is the button that we observe the click event on. We save the button as a class property so we can access it again the Ajax response handler. Again, we use bindAsEventListener() so we can handle the event within the class.

Listing 13 Using bind() when handling Ajax requests (listing-13.html)
<html>
<head>
<title>Using bind() when handling Ajax requests</title>
<script type="text/javascript" src="/js/prototype.js"></script>
</head>
<body>
<div>
<input type="button" value="Click Me!" id="myButton" />
</div>
<script type="text/javascript">
var MyClass = Class.create({
button : null,
initialize : function(button)
{
this.button = $(button);
this.button.observe('click', this._onButtonClick.bindAsEventListener(this));
},
_onButtonClick : function(e)
{
var options = {
method    : 'get',
onSuccess : this._onSuccess.bind(this)
};
new Ajax.Request('listing-12.php', options);
},
_onSuccess : function(transport)
{
var json = transport.responseJSON;
var msg = json.name + ' is from ' + json.country;
this.button.up().update(msg);
}
});
new MyClass('myButton');
</script>
</body>
</html>

Now when we handle the click event (the _onButtonClick() method), we once again perform an Ajax request. The difference between doing it now and in the last article was that now we use bind() so we can use the MyClass instance in the _onSuccess() method.

When the button is clicked, an Ajax request is performed. When the request is finished, _onSuccess() handles the response and updates the button's container with the JSON data read from the Ajax request.

Binding Functions for Enumerables

In the third article in this series we covered enumerables, including the each() method. Because a separate function (known as an iterator) is called when you use each(), once again any classes you are using lose their context.

Consider the code in Listing 14. In the class constructor I have defined an array of different colours. I then attempt to loop over them and call the display() method for each colour. The problem is, this inside the each loop doesn't refer to the MyClass instance.

Listing 14 The problem with using each() in classes (listing-14.html)
<html>
<head>
<title>The problem with using each() in classes</title>
<script type="text/javascript" src="/js/prototype.js"></script>
</head>
<body>
<div id="container"></div>
<script type="text/javascript">
var MyClass = Class.create({
container : null,
initialize : function(container)
{
this.container = $(container);
var colors = [ 'red', 'green', 'blue' ];
colors.each(function(color) {
this.display(color);
});
},
display : function(msg)
{
this.container.insert(msg + '<br />');
}
});
new MyClass('container');
</script>
</body>
</html>

When you use the each() method, it is possible to pass a second argument in order to specify the context. That is, you would use myEnum.each(myFunction, myObject). Or in this specific case, colors.each(function(color) { ... }, this). This is shown in Listing 15.

    <head>
<title>Binding enumerable function handlers</title>
<script type="text/javascript" src="/js/prototype.js"></script>
</head>
<body>
<div id="container"></div>
<script type="text/javascript">
var MyClass = Class.create({
container : null,
initialize : function(container)
{
this.container = $(container);
var colors = [ 'red', 'green', 'blue' ];
colors.each(function(color) {
this.display(color);
}, this);
},
display : function(msg)
{
this.container.insert(msg + '<br />');
}
});
new MyClass('container');
</script>
</body>
</html>
<html>
Note: Technically, you can use the bind() method if you prefer, rather than using this second argument. That is, colors.each(function() { ... }.bind(this)).

Creating a String Representation of Your Class

If you want to define a string representation of an instance of your class, you can do so by defining a class method called toString(). This method must return a string.

Effectively what this does is define what value should be returned when your object is converted (cast) to a string. Listing 16 shows an example of how you might choose to implement toString(). Listing 17 shows how the object is displayed when converted to a string.

Listing 16 Declaring the toString() method (Person.js)
var Person = Class.create({
name : null,
initialize : function(name)
{
this.setName(name);
},
toString : function()
{
return this.getName();
},
setName : function(name)
{
this.name = name;
},
getName : function()
{
return this.name;
},
getCountry : function()
{
return 'Unknown';
}
});
Listing 17 Converting an object to a string so it can be displayed (listing-17.html)
<html>
<head>
<title></title>
<script type="text/javascript" src="/js/prototype.js"></script>
<script type="text/javascript" src="/js/Person.js"></script>
</head>
<body>
<div id="foo"></div>
<script type="text/javascript">
var me = new Person('Quentin Zervaas');
$('foo').update(me);
</script
					  					  
					  
 
 
Add a Comment:
 
(You must be signed in to comment on an article. Not a member? Click here to register)
   
Title:

Comments: