Object-oriented JavaScript

JavaScript is a prototype-based object-oriented language. You can use Caplin Trader 4’s Topiarist library to model classes, interfaces, and inheritence in JavaScript. You should use Topiarist in preference to the Bootstrap.js methods caplin.extend and caplin.implement, the use of which is now deprecated. The caplin.extend and caplin.implement methods will be removed in a future major release of Caplin Trader.

Topiarist library

Topiarist is the recommend library for writing object-oriented JavaScript in Caplin Trader 4.

For an overview of Topiarist, see the pages below:

Bootstrap.js methods (deprecated)

This section provides an overview of writing object-oriented JavaScript using the deprecated methods caplin.extend and caplin.implement.

Declaring interfaces

Declaring an interface is essentially the same as declaring a class. With interfaces though, it’s important to provide feedback when an arbitrary method has not been implemented. You can do that by using br.util.Utility.interfaceMethod(), within the body of each of your interface’s methods.

Using Animal as an example interface, your definition could look something like this:

var brUtility = require('br/util/Utility');

function Animal() {
}

Animal.prototype.speak = function() {
    brUtility.interfaceMethod('novox.Animal' , 'speak');
};

Animal.prototype.getGender = function() {
    brUtility.interfaceMethod('novox.Animal' , 'getGender');
};

Animal.prototype.getLegCount = function()    {
    brUtility.interfaceMethod('novox.Animal' , 'getLegCount');
};

module.exports = Animal;

If another class implements the Animal interface, it must implement all its methods as well, or it will not be able to invoke them. For example, if a class implements Animal, but does not provide an implementation for the method speak(), any invocation to speak will throw an exception with the message "Unexpected exception (This is an interface method (novox.Animal.speak))".

Implementing interfaces

You can implement an interface using the caplin.implement method. You can use caplin.implement to implement as many interfaces as you wish.

If we wanted to declare a Quadruped class that implements the Animal interface, we could do it like this:

var Animal = require('novox/Animal');

function Quadruped(sGender) {
    this.m_sGender = sGender;
};
caplin.implement(Quadruped, Animal);

Quadruped.prototype.getLegCount = function() {
    return 4;
};

Quadruped.prototype.getGender = function() {
    return this.m_sGender;
};

module.exports = Quadruped;

Note that it’s important to implement the interface before any method declarations for it to work properly. It’s also important to provide the actual JavaScript constructor functions to caplin.implement rather than the function names as strings. You may also notice the Quadruped class hasn’t implemented the speak() method, which effectively makes this an abstract class.

Extending classes

You can extend a class using the caplin.extend() method. You should declare the class extension before you declare any of its methods.

As Quadruped is an abstract class, we are going to need to extend it to provide a concrete implementation. Let’s extend Quadruped to give us two concrete classes of Quadruped: Cat and Dog.

The Cat class:

var Quadruped = require('novox/Quadruped');

function Cat(sGender) {
};
caplin.extend(Cat, Quadruped);

Cat.prototype.speak = function() {
    alert('Meow!');
}

module.exports = Cat;

The Dog class:

var Quadruped = require('novox/Quadruped');

function Dog(sGender) {
}
caplin.extend(Dog, Quadruped);

Dog.prototype.speak = function() {
    alert('Woof!');
};

module.exports = Dog;

By extending the Quadruped class and implementing the remaining method from the Animal interface, each of these new classes is complete and we could create instances of them.

Calling super constructors

The JavaScript method apply allows us to call a super constructor while retaining the correct this pointer.

We can call the super Quadruped constructor from within the Dog and Cat constructors by calling Quadruped.apply(this, arguments).

The rewritten Dog constructor:

function Dog(sGender) {
    Quadruped.apply(this, arguments);
};

The rewritten Cat constructor:

function Cat(sGender) {
    Quadruped.apply(this, arguments);
};

If we added any additional arguments at a later date, using the arguments keyword ensures that they would all be passed back to the Quadruped super-constructor without having to explicitly add them to the apply() method in each constructor.

Overriding super methods

The JavaScript method call allows us to call super methods while retaining the correct this pointer.

By default, our cats and dogs all have four legs, a feature that they inherit from the Quadruped class. If we wanted to allow for the occasional three-legged dog we might encounter, the following code would allow us to override Quadruped.getLegCount for three-legged dogs, but still call Quadruped.getLegCount for four-legged dogs:

Dog.prototype.getLegCount = function() {
    if (this.is3LeggedDog()) {
        return 3;
    }
    return Quadruped.prototype.getLegCount.call(this);
};

Writing static classes and enums

It may be useful to provide static classes and enums to complement particular classes and interfaces. You can write static classes and enums by adding properties and methods to a constructor function rather than to the constructor function’s prototype.

For example, we may want to provide an AnimalGender enum to be used in conjunction with the Animal interface:

function AnimalGender() {
}

AnimalGender.MALE = 'male';
AnimalGender.FEMALE = 'female';

module.exports = AnimalGender;