In JS+, classes are defined using the ClassFactory class. The ClassFactory, located in the JS.lang package, is a static class, and cannot be instantiated. It has a single method, createClass(). Let's do a very simple example where we're creating a class to store a two dimensional location-an x coordinate and a y coordinate:
JS.lang.ClassFactory.createClass({
name: "Point"
});
In the above example, we've passed to the createClass() method a single object with a field called 'name'. This is the name of the class that we are creating. It's as simple as that.
To create an object from the new class we've just defined (technically known as instantiating the object) we use JavaScript's new keyword:
var myPoint = new Point();
This is pretty straightforward, but the class we've created doesn't really do anything. Let's add some functionality.
Now that we've created the class-let's create two fields, x and y.
JS.lang.ClassFactory.createClass({
name: "Point",
fields: [{
name: "x"
},{
name: "y"
}]
});
var myPoint = new Point();
myPoint.x = 10;
myPoint.y = 5;
As you can see above, there is a new property that is part of the object that passed to the ClassFactory, fields. fields is an array of object literals that contain the name of each object.
There is common problem with the above example; in JS+, fields are private by default-they aren't exposed. Then why doesn't the above code generate an error? The reason why is that even though JavaScript doesn't think there is an x or y field on the Point object returns-it assumes you are creating them dynamically by assigning the x and y fields. So how do we fix this?
JS.lang.ClassFactory.createClass({
name: "Point",
fields: [{
name: "x",
access: JS.lang.reflect.Modifier.PUBLIC
},{
name: "y",
access: JS.lang.reflect.Modifier.PUBLIC
}]
});
var myPoint = new Point();
myPoint.x = 10;
myPoint.y = 5;
Be very careful about this-it's very easy to get tripped up on.
These field object literals can contain other properties-we'll look at them later.
This is a good start, but what we really should do is access the x and y variables through getters and setters. In other words, we need methods.
Methods follow a similar pattern to the one given for fields above-we pass an array of object literals that contain the name, code and parameters of each method. See below.
JS.lang.ClassFactory.createClass({
name: "Point",
fields: [{
name: "x"
},{
name: "y"
}],
methods:[{
name: "getX",
code: function()
{
return x;
}
},{
name: "getY",
code: function()
{
return y;
}
},{
name: "setX",
params: ["NewX"],
code: function()
{
x = NewX;
}
},{
name: "setY",
params: ["NewY"],
code: function()
{
y = NewY;
}
}]
});
var myPoint = new Point();
myPoint.setX(10);
myPoint.setY(5);
alert(myPoint.getY());
You'll notice that we didn't have to specify an access scope for the methods; that's because in JS+, methods are public by default. We also removed the access scope lines for the fields, making those elements private again. What happens when you call getX() before calling setX()? Like any variable that's referenced before you set it, you would get undefined. Let's prevent that situation from happening by assigning default values of 0 to x and y.
Assigning default values for fields is simple-each field object literal we create, we simply include a defaultValue property. See below:
JS.lang.ClassFactory.createClass({
name: "Point",
fields: [{
name: "x",
defaultValue: "0"
},{
name: "y",
defaultValue: "0"
}],
methods:[{
name: "getX",
code: function()
{
return x;
}
},{
name: "getY",
code: function()
{
return y;
}
},{
name: "setX",
params: ["NewX"],
code: function()
{
x = NewX;
}
},{
name: "setY",
params: ["NewY"],
code: function()
{
y = NewY;
}
}]
});
var myPoint = new Point();
alert(myPoint.getX());
It's important to note that the default value, even if it is a number, must be set as a string. This will likely change in future versions.
We've looked at setting default values for fields, but often times you'll want to perform more complex initialization. How do we do that? By using a constructor method. In JS+, constructors are methods with a specific name: init. See below:
JS.lang.ClassFactory.createClass({
name: "Point",
fields: [{
name: "x",
defaultValue: "0"
},{
name: "y",
defaultValue: "0"
}],
methods:[{
name: "init",
params: ["NewX", "NewY"],
code: function()
{
x = NewX;
y = NewY;
}
},{
name: "getX",
code: function()
{
return x;
}
},{
name: "getY",
code: function()
{
return y;
}
},{
name: "setX",
params: ["NewX"],
code: function()
{
x = NewX;
}
},{
name: "setY",
params: ["NewY"],
code: function()
{
y = NewY;
}
}]
});
var myPoint = new Point(10, 20);
alert(myPoint.getX());
When you create a method called init, this method is automatically called when the object is instantiated.
JS+ also allows you to create static elements.
JS.lang.ClassFactory.createClass({
name: "Speaker",
methods:[{
name: "sayHello",
access: JS.lang.reflect.Modifier.STATIC
code: function()
{
alert("Hello!");
}
}]
});
Speaker.sayHello();
Lastly, but most importantly, JS+ supports a single inheritance model.
JS.lang.ClassFactory.createClass({
name: "Animal",
methods:[{
name: "running",
code: function()
{
alert("I'm running!");
}
}]
});
JS.lang.ClassFactory.createClass({
name: "Mammal",
inherits: "Animal"
}]
});
var dog = new Mammal();
dog.running();
Even if you don't specify a parent class in your class definition, your class will inherit from a built-in class called JSObject. JSObject is the top-level parent class of all classes in JS+.
The ClassFactory in JS+ builds an object-oriented representation of your class that you can examine at runtime. Every class inherits a method called getClass() from JSObject. This method returns a Class object, which you can use to examine the class' constructor, methods & fields. See the JS.lang.reflect API for more details.