Classical Object-Oriented
JavaScript Framework

Download 0.2.7
Released: 26 Oct 2015

GNU ease.js is a Classical Object-Oriented framework for JavaScript, intended to eliminate boilerplate code and “ease” the transition into JavaScript from other Object-Oriented languages. Features include:

GNU ease.js is a framework, not a compiler. It may be used wherever JavaScript may be used, and supports all major browsers; ease.js also provides support for older, pre-ES5 environments by gracefully degrading features (such as visibility support) while remaining functionally consistent.

This project is part of the GNU Project.

Simple and Intuitive Class Definitions

Class definitions closely resemble the familiar syntax of languages like Java and PHP.

var Class = easejs.Class;

var Stack = Class( 'Stack',
{
    'private _stack': [],

    'public push': function( value )
    {
        this._stack.push( value );
    },

    'public pop': function()
    {
        return this._stack.pop();
    },
} );

Classes can be anonymous or named, the latter being more useful for debugging. Since classes may be anonymous, constructors are styled after PHP.

var Foo = Class(
    'private _name': '',

    __construct: function( name )
    {
        this._name = ''+( name );
    },

    'public sayHello': function()
    {
        return this._name + " says 'Hello!'";
    },
);

Classes can be instantiated with or without the new keyword. Omission aids in concise method chaining and the use of temporary instances.

var inst = Foo( "John Doe" );
var inst = new Foo( "John Doe" );

// temporary instance
Foo( "John Doe" ).sayHello();
→ Read more in manual

Classical Inheritance

Classes can be extended to create subtypes. Like C++, methods are not virtual by default. In Java terminology, all methods are final by default. Multiple inheritance, like Java, is unsupported (see Interfaces).

var Cow = Class( 'Cow',
{
    'virtual public tip': function()
    {
        return "Omph.";
    },
} );

var SturdyCow = Class( 'SturdyCow' )
    .extend( Cow,
{
    'override public tip': function()
    {
        return "Moo.";
    },
} );

Alternatively, if creating an anonymous subtype, the supertype's extend() method may be used.

var SturdyCow = Cow.extend( { /*...*/ } );

Type checks for polymorphic methods may be performed with Class.isA(), which is recommended in place of instanceof.

var cow    = Cow(),
    sturdy = SturdyCow();

Class.isA( Cow, cow );           // true
Class.isA( SturdyCow, cow );     // false
Class.isA( Cow, sturdy );        // true
Class.isA( SturdyCow, sturdy );  // true

To prevent a class from being extended, FinalClass may be used.

var Foo = FinalClass( 'Foo',
{
    'public describe': function()
    {
        return "I cannot be extended.";
    },
} );
→ Read more in manual

Abstract Classes and Methods

If a class contains abstract members, it must be declared as an AbstractClass. Abstract methods must be overridden by subtypes and are implicitly virtual.

var Database = AbstractClass( 'Database',
{
    'public connect': function( user, pass )
    {
        if ( !( this.authenticate( user, pass ) ) )
        {
            throw Error( "Authentication failed." );
        }
    },

    // abstract methods define arguments as an array of strings
    'abstract protected authenticate': [ 'user', 'pass' ],
} );

var MongoDatabase = Class( 'MongoDatabase' )
    .extend( Database,
{
    // must implement each argument for Database.authenticate()
    'protected authenticate': function( user, pass )
    {
        // ...
    },
} );
→ Read more in manual

Interfaces

ease.js supports the Java concept of Interfaces, which act much like abstract classes with no implementation. Each method is implicitly abstract. Properties cannot be defined on interfaces.

var Filesystem = Interface( 'Filesystem',
{
    'public open': [ 'path', 'mode' ],

    'public read': [ 'handle', 'length' ],

    'public write': [ 'handle', 'data' ],

    'public close': [ 'handle' ],
} );

Concrete classes may implement one or more interfaces. If a concrete class does not provide a concrete implementation for every method defined on the interface, it must be declared an AbstractClass.

var ConcreteFilesystem = Class( 'ConcreteFilesystem' )
    .implement( Filesystem )  // multiple interfaces as separate arguments
{
    'public open': function( path, mode )
    {
        return { path: path, mode: mode };
    },

    'public read': function( handle, length )
    {
        return "";
    },

    'public write': function( handle, data )
    {
        // ...
        return data.length;
    },

    'public close': function( handle )
    {
        // ...
        return this;
    },
} );

Polymorphic methods may check whether a given object implements a certain interface.

var inst = ConcreteFilesystem();
Class.isA( Filesystem, inst ); // true
→ Read more in manual

Access Modifiers

All three common access modifiers—public, protected and private—are supported, but enforced only in ECMAScript 5 and later environments.

var DatabaseRecord = Class( 'DatabaseRecord',
{
    'private _connection': null,


    __construct: function( host, user, pass )
    {
        this._connection = this._connect( host, user, pass );
    },

    'private _connect': function( host, user, pass )
    {
        // (do connection stuff)
        return { host: host };
    },

    'protected query': function( query )
    {
        // perform query on this._connection, rather than exposing
        // this._connection to subtypes
    },

    'protected escapeString': function( field )
    {
        return field.replace( "'", "\\'" );
    },

    'public getName': function( id )
    {
        return this._query(
            "SELECT name FROM users WHERE id = '" +
            this._escapeString( id ) + "' LIMIT 1"
        );
    },
} );

In the above example, the database connection remains encapsulated within DatabaseRecord. Subtypes are able to query and escape strings and external callers are able to retrieve a name for a given id. Attempting to access a private or protected member externally will result in an error. Attempting to access a private member from within a subtype will result in an error.

Alternatively, a more concise style may be used, which is more natural to users of JavaScript's native prototype model:

var DatabaseRecord = Class( 'DatabaseRecord',
{
    /* implicitly private */
    _connection: null,


    __construct: function( host, user, pass )
    {
        this._connection = this._connect( host, user, pass );
    },

    /* implicitly private */
    _connect: function( host, user, pass )
    {
        // (do connection stuff)
        return { host: host };
    },

    'protected query': function( query )
    {
        // perform query on this._connection, rather than exposing
        // this._connection to subtypes
    },

    'protected escapeString': function( field )
    {
        return field.replace( "'", "\\'" );
    },

    /* public by default */
    getName: function( id )
    {
        return this._query(
            "SELECT name FROM users WHERE id = '" +
            this._escapeString( id ) + "' LIMIT 1"
        );
    },
} );
→ Read more in manual

Static and Constant Members

Static members are bound to the class itself, rather than a particular instance. Constants are immutable static members (unlike languages like PHP, they may use any access modifier). In order to support both pre- and post-ECMAScript 5 environments, the syntax requires use of a static accessor method—$().

var Cow = Class( 'Cow',
{
    'const LEGS': 4,

    'private static _number': 0,

    __construct: function()
    {
        // __self refers to the class associated with this instance
        this.__self.$( '_number' ) = this.__self.$( 'number' ) + 1;
    },

    'public static create': function()
    {
        return Cow();
    },

    'public static getNumber': function(){
    {
        return this.__self.$( '_number' );
    },
} );

Cow.$( 'LEGS' ); // 4
Cow.getNumber(); // 0
Cow.create();
Cow.getNumber(); // 1
→ Read more in manual

Traits As Mixins

Trait support was introduced in celebration of becoming a GNU project. It is currently under development and has not yet been finalized, but has been included in each GNU ease.js release since v0.2.0, and is stable.

Documentation will be available once some final details are finalized. Until that time, the test cases provide extensive examples and rationale. The following posts also summarize some of the features: