tmclasses: my javascript class library

I’ve decided to take my JavaScript class system out of my more general JavaScript library, tmlib. It can now be found in my js-tmclasses repository. I felt it needed to be separated to make it easier to work on without the clutter of all of the other functionality in tmlib. It also will be easier for others to make use of, not being buried among a bunch of other junk and not having all of that junk in the default build file. This decision was made easier since I converted to using require.js a while back. I can include tmclasses in a vendor folder, pull into tmlib and attach everything to the same places in tmlib to provide the same interface as before, and it is basically the same as it had been usage-wise. The build is slightly heavier, but that is partly due to duplication of methods that I will eliminate or limit.

tmclasses is a fairly simple class system so far. It provides a method for creating ‘classes’, which in JavaScript are basically functions with properties attached to their prototypes. The feature set is basic, but I intend to add more to it to get some of the niceties of Qooxdoo’s class system. I like a lot of things about Qooxdoo’s class system, but found it hard

Usage

The heart of tmclasses is the create method. It is passed an argument with all of the configuration to create a class.

Properties

The most important configuration option is the ‘properties’. It is an object whose properties are attached to the prototype of the class. It is used to attach both methods and attributes to the class.

MyClass = tmclass.create({
    properties: {
        foo: 'default value'
        ,getFoo: function(){
            return this.foo;
        }
        ,setFoo: function(_value){
            this.foo = _value;
        }
    }
});

is equivalent to the plain JavaScript:

MyClass = function(){};
MyClass.prototype.foo = 'default value';
MyClass.prototype.getFoo = function(){
    return this.foo;
};
MyClass.prototype.setFoo: function(_value){
    this.foo = _value;
};

They are attached to the prototype, so you get the many advantages that brings. You get the automatic inheritance that provides. You could mutate the prototypes and have the changes applied to existing instances. It allows instanceof to work:

myInstance = new MyClass();
console.log(myInstance instanceof MyClass);

If the value of one of these properties is an object, it is treated as a configuration for the property rather than the value, similar to how Qooxdoo properties work. Right now, this is not fully implemented, so all that works is the ‘init’ key, which defines what value the prototype property is set to. I plan on attaching features like Qooxdoo has, such as callbacks for when the property is mutated, events that can be fired when the property is changed, etc.

Constructor

The ‘init’ property serves as the constructor. It is run when the actual function that serves as the class is called, which is generated by the create method.

MyClass = tmclasses.create({
    init: function(_options){
        this.options = _options;
    }
});

is equivalent to:

MyClass = function(_options){
        this._options = _options;
});

Mixins

One or more class definitions similar to the one used by create can be passed in by attaching them to the ‘mixins’ property (or ‘preMixins’ or ‘postMixins’). Right now this isn’t fully implemented for anything but statics and properties. I’d like it to be able to add init methods, and I’d like it to be able to mix in plain objects that aren’t in the configuration format, where all properties will be mixed in. Right now, mixins can work like:


MixinA = {properties: {a: 'a'}};
MixinB = {properties: {b: 'b'}};
MixinC = {properties: {c: 'c'}};
MixinD = {properties: {d: 'd'}};
MixinE = {properties: {e: 'e'}};

MyClass = tmclasses.create({
    preMixins: [
        MixinA
        ,MixinB
    ]
    ,mixins: [
        MixinC
    ]
    ,properties: {
        c: 'property c'
        ,d: 'property d'
    }
    ,postMixins: [
        MixinD
        ,MixinE
    ]
});

which might be implemented in the following way in regular JavaScript (with the help of jQuery.extend and the above mixins for brevity):

MyClass = function(){};
jQuery.extend(MyClass.prototype, MixinA);
jQuery.extend(MyClass.prototype, MixinB);
jQuery.extend(MyClass.prototype, MixinC);
jQuery.extend(MyClass.prototype, {
    c: 'property c'
    ,d: 'property d'
});
jQuery.extend(MyClass.prototype, MixinD);
jQuery.extend(MyClass.prototype, MixinE);

The ‘postMixins’ will overwrite the classes ‘properties’.

Parent

The ‘parent’ key provides the object that the class will inherit from. tmclasses uses prototypical inheritance, so instanceof still works and all of the other niceties of prototypical inheritance.

MyClass = tmclasses.create({
    init: function(){ this.args = arguments; }
});
MyChildClass = tmclasses.create({
    parent: MyClass
});

is similar to:

MyClass = function(){ this.args = arguments; };
MyChildClass = function(){};
MyChildClass.prototype = new MyClass();

except that the parent constructor is not run (the ‘init’ of MyClass, so MyChildClass wouldn’t have ‘args’ in its prototype with tmclass.create()).

I have also set up a convenience method __parent that you can call within methods to call that same method of the parent class.

MyClass = tmclasses.create({
    init: function(){ this.args = arguments; }
});
MyChildClass = tmclasses.create({
    init: function(){
        this.__parent(arguments);
        this.foo = 'bar';
    }
});
myInstance = new MyChildClass('foo', 'bar');
console.log(myInstance.args[0] === 'foo');
console.log(myInstance.args[1] === 'bar');
console.log(myInstance.foo === 'bar');

Statics

These are just properties attached to the class function itself instead of to the prototype. There is currently no way to access them from the instance directly, though I plan to add one.

MyClass.create({
    statics: {
        foo: 'foo'
    }
});
console.log(MyClass.foo === 'foo');

is like:

MyClass = function(){};
MyClass.foo = 'foo';
console.log(MyClass.foo === 'foo');

BaseClass

I have a class that all classes inherit from by default if the module is loaded. It’s called BaseClass. It will add more niceties in the future, but right now the main thing it does is take the first argument to ‘init’ as an object and attach all its properties to the new instance.

MyClass = tmclasses.create();
myInstance = new MyClass({
    foo: 'foo'
    ,bar: 'bar'
});
console.log(myInstance.foo === 'foo');
console.log(myInstance.bar === 'bar');

Thoughts

I’ve put more time into this class system than I probably ought to have, considering the many that are out there and the power of the Qooxdoo system. But it is nice and simple and handles my needs for the web site work I do at my job. It will be able to expand to fit my needs. I have a lot more I want to do with it, like adding some nice features from Qooxdoo into the base create method or BaseClass. I also may add some mixins and possibly some other base classes, though those may end up in tmlib to keep tmclasses lean.