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
Simulate abstract classes and methods
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 Ellipse 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.
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 );.
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).
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.
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.
01 alert( Rational.prototype.constructor ); 02 alert( r1 instanceof Object ? "Ok" : "Ko" );
constructor
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.
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.
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 Ellipse 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 Ellipse framework performs, for an Ellipse Web page, the change
of this Javascript file implicitly (i refer you, in this regard, on the chapter Importing the Ellipse Javascript files).
01 var Rational = Object.extendClass( {
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 = Rational.extendClass( {
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).
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.
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.
Javascript doesn't proposes, strictly speaking, the concepts of classes and abstract methods. However, it is possible to get closer. But it should be noted that there is an important difference: the redefinition of an abstract method is normally guaranteed by the compiler your language (Java, why not). Or Javascript is not a compiled language: it is therefore necessary to treat this aspect in the implementation (at the runtime) of your program.
For example, we will take a specific case implemented in the JWT library. Similar to Java, the concept of JWT Container
(pseudo-class "corelib/services/web/javascript/jwt/Container.js") proposes the concept of placement strategy for the components
of the container (known as layout). Any class of layout must provide a particular method:
layoutContainer( contanier ). However this method can't be implemented if we don't know the exact nature of the placement strategy.
However, the JWT library provided the pseudo class corelib/services/web/javascript/jwt/LayoutManager.js which you can see a short extract.
var LayoutManager = Object.extendClass( {
// . . . du code . . .
layoutContainer : function ( container ) {
throw new AbstractMethodException();
}
});
You can note the presence of the instruction throw that is used to launch an exception of type AbstractMethodException if this
method is invoked: this is like this we can introduced the concept of abstract method in JWT. Note that nothing marks the LayoutManager class as
being abstract: we can do nothing in Javascript to approach this concept. So, here is an extract code that shows you a "concrete" class
of layout.
importPackage( "corelib/services/web/javascript/jwt/LayoutManager.js" );
var BorderLayout = LayoutManager.extendClass( {
// . . . du code . . .
layoutContainer : function ( container ) {
// . . . du code . . .
}
});
Finally here's how to ensure that the method setLayout( layout ) of the pseudo class Container will be always
invoked with a derivative request of LayoutManager. You can note there the presence of the exception BadTypeException.
importPackage( 'corelib/services/web/javascript/jwt/Component.js' );
var Container = Component.extendClass( {
// . . . du code . . .
setLayout : function ( layout ) {
if ( layout != null && layout instanceof LayoutManager == false ) {
throw new BadTypeException( "Not a LayoutManager" );
}
// . . . du code . . .
},
// . . . du code . . .
});
|
|
|||||||