WARNING: This tutorial is being written! Do not hesitate to report any errors or suggestions.
Quick access: Reminders on object oriented programming in Javascript The concept of prototype in Javascript Inheritance and polymorphism in Javascript The concept of data type within the JWT API
We will are interested, in this document, in the object oriented programming in Javascript. To do this, we will start by recalling the basic concepts inherent in Javascript, and then we will see how the NWS framework and the JWT API can simplify things in your objects developments in Javascript. This chapter will also help you to better understand how the different types of JWT graphical components have been developed (Components of menu bars, component of display RSS feeds, ...).
JavaScript is an object oriented language: it provides you a set of syntactic constructs for implementing this model of programming. Nevertheless, it should also be noted that Javascript is a weakly typed language: we don't type the variables, but these are the datas themselves that have the types informations. It follows (in part) a remarkable characteristic: we don't have, at a strictly speaking, any concepts of class in Javascript. Hence the question that you ask yourself certainly: how to make an object oriented programming without the concept of class?
The solution chosen by the designers of Javascript is to say that an object is in fact an associative array: for a key (alphanumeric) we associate it a value. If more we propose several syntax for accessing these associative arrays (these objects) it should be possible to approach a model of object oriented programming classical and usual. You should be aware that the object model of Javascript is fairly limited (apart from the fact that there not stand the notion of class, the level of visibility of the members (public, private, ...) are not supported either. But we could be doubt about of this: if an object is a table, we can access to each of its inputs (or each of its members).
The following example shows you, in different ways, how to create and enhance an object. Note that I juggle permanently on the different syntaxes allowed. Also note that in Javascript all is given, including a function: it is what makes possible to add function as a value for an entry of an associative array (see lines 08 and 20).
01 //--- Array like syntaxe --- 02 03 var rationalNumber1 = {}; 04 05 rationalNumber1[ "numerator" ] = 3; 06 rationalNumber1[ "denominator" ] = 2; 07 08 rationalNumber1[ "toString" ] = function() { 09 return "[" + this["numerator"] + "/" + this["denominator"] + "]"; 10 } 11 12 var result = rationalNumber1["toString"](); 13 14 //--- The same with object like syntaxe --- 15 var rationalNumber2 = new Object(); 16 17 rationalNumber2.numerator = 3; 18 rationalNumber2.denominator = 2; 19 20 rationalNumber2.toString = function() { 21 return "[" + this.numerator + "/" + this.denominator + "]"; 22 } 23 24 result = rationalNumber2.toString(); 25 26 //--- You can also do this -- 27 result = rationalNumber1.toString(); 28 result = rationalNumber2["toString"]();
The problem with this technique (whatever the option being considered) is in the fact that it is difficult to reuse (apart from making the copy and paste) to produce multiple instances of the same type (I do not speak, voluntarily, about a class). If we want to simulate the notion of object type, we have to use another feature of the Javascript language: if a function is used in conjunction with the operator new, then this function will serve as the function for the object construction (like a constructor in Java) and during its implementation, the variable this will represent the object being initialized. The new will allocate the memory whereas the function will initialize the memory space considered. Here is a small example.
new
this
01 //--- Constructor function --- 02 function Rational( numerator, denominator ) { 03 04 // Two attributes 05 this.numerator = numerator; // this["numerator"] is supported too 06 this.denominator = denominator; 07 08 // One method 09 this.toString = function() { 10 return "[" + this.numerator + "/" + this.denominator + "]"; 11 } 12 13 } 14 15 //--- Objet manipulations --- 16 var r1 = new Rational( 3, 2 ); 17 var result = r1.toString();
I clearly insist on the fact that in JavaScript, the variables and therefore the parameters are not typed. Suddenly, in line 02, the function Rational accepts two parameters (numerator and denominator, not typed). Little additional information: in reality Rational is not a function but rather a method. With Javascript all is object (or associative array, it's like you want): therefore the method Rational is a member of the object window (at least in the context of a browser). The line 16 would have been written otherwise: var r1 = new window.Rational( 3, 2 );.
Rational
numerator
denominator
window
var r1 = new window.Rational( 3, 2 );
Since Javascript 1.5 (if I am not mistaken), a new option has emerged: the concept of a prototype. This concept is an extension of the possibilities of the Javascript object model, and allows to approach more to the notion of class, but in a particularly way. A prototype is added as a property of a function of building objects (once again, everything is object in Javascript). Thus this construction function works just now like an object constructor while his prototype supports the definition part of the behaviour (the methods). Here is a little example once again based on the definition of rational numbers. Note that when you set a prototype, it is traditional to use the associative syntax (key/value) to the table definition (lines 11 and 15). The two previous syntaxes have nevertheless been used.
01 //--- Constructor function --- 02 function Rational( numerator, denominator ) { 03 04 // Two attributes 05 this.numerator = numerator; // this["numerator"] is autorized too 06 this.denominator = denominator; 07 } 08 09 Rational.prototype = { 10 // One method 11 toString: function() { 12 return "[" + this.numerator + "/" + this.denominator + "]"; 13 }, 14 15 simplify: function() { 16 var maxBound = Math.min( this.numerator, this.denominator ); 17 maxBound = Math.ceil( maxBound / 2 ); 18 19 var i = 2; 20 while( i < maxBound ) { 21 if ( this.numerator%i == 0 && this.denominator%i == 0 ) { 22 this.numerator /= i; 23 this.denominator /= i; 24 continue; 25 } 26 i++; 27 } 28 } 29 30 } 31 32 //--- Objet manipulations --- 33 var r1 = new Rational( 24, 12 ); 34 r1.simplify(); 35 var result = r1.toString();
You can note that with this technique, if we add a member to the prototype considered, then that member becomes accessible to all the requests that are associated with it. Thus the following example adds a method contains to all the requests of the type Array (this type is intrinsic to Javascript and can set up some tables in various sizes).
contains
Array
01 var tb = new Array(); 02 03 if ( ! Array.prototype.contains ) { 04 Array.prototype.contains = function( value ) { 05 for( var i=0; i<this.length; i++ ) { 06 if ( value == this[i] ) return true; 07 } 08 return false; 09 } 10 } 11 12 tb.push( "Hello" ); 13 tb.push( "World" ); 14 var exists = tb.contains( "Hello" );
To understand the inheritance model of Javascript, we need to understand a few additional points. These are the points that I tried to synthesize in the diagram below. Some explanations will follow.
So resume this diagram point by point. We start with the construction function of the request of Object type: because any Javascript object exposes some common methods (toString particular), there is a little doubt that the object function has a prototype defining the common members. If you evaluate the expression Object.prototype, you will find this request of prototype.
toString
Object.prototype
We will see now now our function of construction of rational number: it also has a prototype, we have also explicitly created it. The important question to ask is how we got this request: in fact at the base, this request is of type Object. Whatever the syntax used ({} or new Object() - see the first example of this chapter), we reached an Object. To be convinced, i suggest you to test the following example (it is an additional code to the previous example): the property constructor of an object makes it possible to recover the function of construction that have served to produce it.
Object
{}
new Object()
constructor
01 alert( Rational.prototype.constructor ); 02 alert( r1 instanceof Object ? "Ok" : "Ko" );
Through this, the inheritance has been implemented and a number Rational is a Javascript Object. Indeed it is possible to use the operator (see line 02 of the above example) to validate the polymorphic appearance of a Javascript object (an example showing "Ok" of course). So we can do the same in deriving a particular type of rational number.
01 //--- Constructor function --- 02 function MyRational( numerator, denominator ) { 03 this.parent = Rational; 04 this.parent( numerator, denominator ); 05 } 06 07 //--- MyRational prototype création --- 08 MyRational.prototype = new Rational(); 09 10 //--- MyRational prototype extension --- 11 MyRational.prototype.newMember = function() { 12 alert( "OK - " + this ); 13 } 14 15 16 //--- Objet manipulations --- 17 var r2 = new MyRational( 24, 12 ); 18 r2.simplify(); 19 r2.newMember(); 20 21 alert( r2 instanceof Object ? "Ok" : "ko" ); 22 alert( r2 instanceof Rational ? "Ok" : "ko" ); 23 alert( r2 instanceof MyRational ? "Ok" : "ko" );
In line 08, we are defined the prototype of the function MyRational as a request of Rational, so the inheritance is implemented. In line 11, we enrich the prototype of MyRational. As to the origin we acquired the prototype by creating a new instance (line 08), the method newMember therefore will not be accessible from Rational.
MyRational
newMember
I emphasize that the polymorphism (and the dynamic linking - late binding) is used at different points on this program: on line 12, it is the toString defined in Rational that will be invoked. And on lines 21 to 23, you can note the use of the operator instanceof.
instanceof
Remember, of course, that all the aspects presented above work very well with the major Web browsers used (including Firefox and IE).
One of the objectives of the NWS Framework is to provide a complete solution for developing Web applications: including aspects for the implementation of the part running on the server (based on the Java language), but also an API providing many Web components running on the client. This API is, of course, developed over Javascript and is named JWT (Javascript Widget toolkit). The following sections will present more seriously this API.
Who said library of advanced components, scalable and maintainable, means of course the conception and object oriented programming: JWT uses, although this paradigm of programming. Nevertheless, this API will not be content with the intrinsic model of object oriented programming in Javascript and some extensions (as well as some conventions) will be used. The example below shows you how, through the object model of the JWT library, we can create a inheritance, guaranteeing of course the polymorphism. It is clear that to run, this piece of code requires the loading of the library corelib/services/web/javascript/Core.js, but I would remind you that the NWS framework performs, for a NWS Web page, the change of this Javascript file implicitly (i refer you, in this regard, on the chapter Importing the NWS Javascript files).
corelib/services/web/javascript/Core.js
01 var Rational = Object.extendClass( new Object(), { 02 03 initialize: function( numerator, denominator ) { 04 this._numerator = numerator || 0; 05 this._denominator = denominator || 1; 06 }, 07 08 toString: function() { 09 return "[" + this._numerator + "/" + this._denominator + "]"; 10 }, 11 12 simplify: function() { 13 var maxBound = Math.min( this._numerator, this._denominator ); 14 maxBound = Math.ceil( maxBound / 2 ); 15 16 var i = 2; 17 while( i < maxBound ) { 18 if ( this._numerator%i == 0 && this._denominator%i == 0 ) { 19 this._numerator /= i; 20 this._denominator /= i; 21 continue; 22 } 23 i++; 24 } 25 } 26 27 getNumerator: function() { 28 return this._numerator; 29 } 30 31 getDenominator: function() { 32 return this._denominator; 33 } 34 35 }); 36 37 var MyRational = Object.extendClass( new Rational(), { 38 39 initialize: function( numerator, denominator ) { 40 Rational.prototype.initialize.call( this, numerator, denominator ); 41 // Continue initialization 42 }, 43 44 newMember: function() { 45 alert( "OK - " + this ); 46 } 47 }); 48 49 //--- Objet manipulations --- 50 var r1 = new MyRational( 24, 12 ); 51 r1.simplify(); 52 r1.newMember(); 53 54 alert( r1 instanceof Object ? "Ok" : "ko" ); 55 alert( r1 instanceof Rational ? "Ok" : "ko" ); 56 alert( r1 instanceof MyRational ? "Ok" : "ko" );
It is important to understand that the method extendClass is not intrinsic to Javascript. This is the JWT framework that has associate it to the prototype of the function Object. Nevertheless it really simplifies the implementing inheritance in your "pseudo classes". In this new approach, the method initialize serves as a constructor. It is implicitly invoked with each new construction of request, if the "pseudo-class" is based on this approach (méthode extendClass).
extendClass
initialize
I draw your attention to the fact that in Javascript, there is no level of visibility (public, private, ...): we have already talked a little. Suddenly, JWT has adopted a convention: the Private Members are prefixed by the character _. There was no reason why you can't access in such a field, but if you do it on a JWT component, then you say that there is certainly a different way of doing things.
public
private
All the JWT components derive from the "pseudo class" Component. In reality JWT is inspired (as its name suggests it) by the AWT library (and Swing) of Java (you will find containers, investment strategies (layout), ...). In the drift of your types of graphical components of Component you inherit from a set of methods that will make your life easier. For more informations, please know that you are going soon have access to a complete documentation of the JWT API.
Component
Dominique LIARD - © 2007 SARL Infini Software - All rights reserved Other brands and product names in these documents are the property of their respective owners.