Using Classes in a very flexible and easy way.
With nodejs:
Create your project using this package.json. After downloaded, run npm install
and start writing your application:
In the browser:
For browser-usage, ITSA has a predefined loaderfiles. Once included, a global ITSA
object with default features is available. For customized loaderfiles, read: Customized build.
<script src="/itsabuild-min.js"></script>
To start with: always favour Favor object composition over class inheritance: read more here by Eric Elliott. Should you choose to use Classes, than read on: this is ITSA's implication of Classes:
To create a class in JavaScript, you would use code like this:
var Shape = function (x, y) {
this.x = x || 0;
this.y = y || 0;
}
Shape.prototype.move = function (x, y) {
this.x += x;
this.y += y;
};
var shape = new Shape(10, 20);
This module replaces this native way in easier and more flexible Classes:
ITSA creates new Classes by ITSA.Classes.createClass()
. This returns an instantiatable Class. Technically, it returns a sub-class of Function with the given constructor and prototype members.
var Shape = ITSA.Classes.createClass(function (x, y) {
this.x = x || 0;
this.y = y || 0;
},{
move: function (x, y) {
this.x += x;
this.y += y;
}
});
Any Class
can be iniatiated with new Classname
. It is very important to use the new
keyword, otherwise very unexpected things may happen:
var myShape = new Shape(5, 10);
myShape.move(20, 30);
Multiple instances are completely independent from each other:
var shape1 = new Shape(5, 10),
shape2 = new Shape(50, 70);
shape1.move(20, 30);
// shape1 --> (20,30)
// shape2 --> (50,70)
Any Class
can be subclassed with 'subClass()`. The subclass has its own constructor and optional extra members. By default, the constructor of the inherited Class gets invoked with the same arguments as what this constructor recieves:
var Circle = Shape.subClass(
function (x, y, r) {
// under the hood, Shape's constructor gets invoked with the arguments: (x, y, r)
// before continuing the following code:
this.r = r || 1;
},{
area: function () {
return this.r * this.r * Math.PI;
}
}
);
var circle1 = new Circle(5, 10, 1),
circle2 = new Circle(50, 70, 2);
circle1.area(); // <-- 3,14
circle2.area(); // <-- 12,57
If you don't want the inherited Class's constructor to be invoked, you can set the 3th argument false. In most cases, you probably want to invoke the inherited constructor manually by using this.$superProp('constructor', arg1, arg2, ...)
.
When subClassing, it is easy to access properties of its parent by invoke this.$superProp(propertyName, args)
. Any property can be invoked: when it's a method, you can pass through its arguments as from the second argument-position. $superProp
is avialabe on the context "this"
.
When a constructor
needs to be subClassed, you can use: this.$superProp('constructor', args)
. Be sure you set the firth argument false
in order to be able to manually invoke the super-constructor.
var Circle = Shape.subClass(
function (radius, x, y) {
// we will manually invoke the super-constructor
this.$superProp('constructor', x, y);
this.radius = radius || 1;
}, null, false
);
If you want to access properties that lie higher in the Class-tree (higer than parent
), you can use this.$super.$superProp()
or multiple $super
parts. $super
is avialabe on the context "this"
.
var Rectangle = Shape.subClass(
function (x, y, l, h) {
this.l = l || 0;
this.h = h || 0;
}
);
var Square = Rectangle.subClass(
function (x, y, l) {
this.$super.$superProp('constructor', x, y);
this.l = l || 0;
}, null, false
);
Existing Classes cannot have their inherited (parent) Class being redefined (just define a new Class in those cases). However, they can have their constructor redefined, or prototype-properties being redefined, extended, or removed.
Re-defines the constructor of an existing Class. From the point this change is made, any new instance will use this constructor. This also counts for sub-classes. setConstructor
accepts the new constructor as its first argument, and optional a second boolean argument to specify if the constructor should be chained (invoking its parent constructor automaticly).
var ClassA, ClassB, ClassC, c;
ClassA = Classes.createClass(function(x) {
this.x = x;
});
ClassB = ClassA.subClass(function(x, y) {
this.y = y;
}, false);
ClassC = ClassB.subClass(function(x, y, z) {
this.z = z;
});
c = new B(1,2,3);
// c.x === undefined
// c.y === 2
// c.z === 3
B.setConstructor(function(x, y) {
this.y = 3*y;
});
c = new B(1,2,3);
// c.x === 1
// c.y === 6
// c.z === 3
It allows to add extra methods to a given class. This is helpful when common functionality needs to be added to multiple classes, without having to inherit from it. For example, the previous example could have been made like this:
var movable = {
move: function (x, y) {
this.x += x;
this.y += y;
},
moveX: function (x) {
this.x += x;
},
moveY: function (y) {
this.y += y;
}
};
var Circle = ITSA.Classes.createClass(
function (x, y, r) {
this.r = r || 1;
},{
area: function () {
return this.r * this.r * Math.PI;
}
}
).mergePrototypes(movable);
The merged methods will not overwrite existing methods unless the second argument is set to true
to force the overwrite.
If the merged methods override existing ones, the original method will be available in the $orig
property, which is avialabe on the context "this"
. This allows plugins that can be refer to the original methods. All arguments you pass into $orig()
will be passed through to its original method.
It is possible to redefine the same method in descendent subClasses by using $orig() over and over again. All original methods will be available.
var ClassA = ITSA.Classes.createClass({
method: function (a) {
return a + 'a';
}
}).mergePrototypes({
method: function (b) {
return this.$orig(b) + 'b';
}
}, true);
var a = new ClassA();
console.log(a.method('1'));
// prints "1ab"
var Circle = ITSA.Classes.createClass(
function (x, y, r) {
this.r = r || 1;
},{
area: function () {
return this.r * this.r * Math.PI;
}
}
);
var c = new Circle(5);
C.removePrototypes('area');
c.area(); // <-- will throw an error: method `area` does not exist
Class-instances can be destroyed with the method destroy()
. By default, this is a NOOP
-method. Whenever a class-instance gets destroyed, every destroy()
up the chain gets invoked. That is, unless you invoke destroy('true'), which does a non-chain destruction. In most cases, you don't need to setup destroy
. Only when you have set data by closure outside the instance (for example in an array), then you need to clean it up: otherwise there would be a memoryleak. Another feature would be when the class-instantiation would create a dom-node, which you need to remove at destruction.
Note that -when creating the destroy
-method, you don't need to specify its only argument. Under the hood, destroy
gets stored as _destroy
, whereas Class.destroy(notChained)
is a method on the BaseClass at the highest position of the Class-chain --> this destroy()
invokes _destroy
of the whole chain.
var regArray = [];
var Registration = ITSA.Classes.createClass(
function (data) {
this.data data;
regArray.push(data);
},{
destroy: function () {
delete regData[this.data];
}
}
);
var registration = new Registration('I got registered');
// regArray.length === 1
registration.destroy();
// regArray.length === 0
When the event-module
is loaded, all Classes become an Event-listener (for more info on event-listeners: see the module Event
). This behaviour is added to the Base-Class which all Classes inherit. The Event-listener makes the following properties available:
The methods named selfxxx
make it posible to invoke the subscriber only when e.target
equals the instance
. This avoids unwanted interaction: see the examples.
To make any Class an Event-emitter, you should merge Event.Emitter()
at the prototype by yourself. This cannot be done by the module, because Event-emitters need an emittername, which is Class-specific.
var Circle = ITSA.Classes.createClass(
function (x, y, r) {
this.r = r || 1;
},{
area: function () {
return this.r * this.r * Math.PI;
}
}
).mergePrototypes(Event.Emitter('circle'));
var c = new Circle(5);
c.emit('drawn'); // <-- will fire the 'circle:drawn'-event
You don't need to detach any listener you have set on any class-instance.
This is done automaticly when you destroy the class by using destroy()
- regardless of its first argument. Under the hood, destroy()
invokes this.detachAll()
which removes all listeners of the instance.