Classes

Creating a new class

Creating classes in Alchemy/Protoblast does not use the new class syntax added to JavaScript a few years ago.

Instead they are created in a functional way.

To create a very simple new class, you can do this:

const MyClass = Function.inherits(function MyClass() {
    // This is the constructor
});

This class will now be available in the global Classes variable:

let instance = new Classes.MyClass();

You can also tell a class to be stored in a specific namespace, for example:

const NamespacedClass = Function.inherits(null, 'Namespace.Alpha', function NamespacedClass() {

});

Now this class can be instanced like this:

let instance = new Classes.Namespace.Alpha();

You probably wonder what the first null argument is. That is used when you want to inherit from another class. You can use a path or another class directly in here.

Let's extend our class:

const ChildClass = Function.inherits('Namespace.Alpha.NamespacedClass', function ChildClass() {
    // Call the parent constructor
    ChildClass.super.call(this);
});

This class can now be instanced like this:

let instance = new Classes.Namespace.Alpha.ChildClass();

The child class is automatically placed in the same namespace as its parent. Though this can be changed:

const OtherNamespaceClass = Function.inherits('Namespace.Alpha.NamespacedClass', 'SomewhereElse', function OtherNamespaceClass() {
    // Call the parent constructor
    OtherNamespaceClass.super.call(this);
});

This class is now here:

let instance = new Classes.SomewhereElse.OtherNamespaceClass();

There is one more trick: if your new class has no special new logic in its constructor, and you just want to use the parent constructor anyway, you can just pass in a string:

const SimpleClass = Function.inherits('SomewhereElse.OtherNamespaceClass', 'SimpleClass'),

The you can do, and it will automatically call the parent constructor:

let instance = new Classes.SomewhereElse.SimpleClass();

Adding methods

Now that you have your class object, you can functionally add new methods to it:

SimpleClass.setMethod(function myMethod() {
    return 'this-is-my-method';
});

This can now be called like this:

let instance = new SimpleClass(); // Or Classes.SomewhereElse.SimpleClass();
console.log(instance.myMethod());>>> "this-is-my-method"

Let's say you want to extend this class and override this method:

const ExtendedClass = Function.inherits('SomewhereElse.SimpleClass', 'ExtendedClass');

ExtendedClass.setMethod(function myMethod() {
    return 'overridden! ' + myMethod.super.call(this);
});

This can now be called like this:

let instance = new ExtendedClass();
console.log(instance.myMethod());>>> "overridden! this-is-my-method"

Adding static methods

Static methods exist on the class object itself, not on instances.

ExtendedClass.setStatic(function myStaticMethod() {
    return 'static!';
});

You can then execute this static method like so:

console.log(ExtendedClass.myStaticMethod());

Adding properties

There are a few ways of adding property getters. The first one:

ExtendedClass.setProperty(function my_property_name() {
    return this._my_property_name;
}, function setter(value) {
    this._my_property_name = 'SET: ' + value;
});

This can then be used like this:

let instance = new ExtendedClass();

console.log(instance.my_property_name);>>> undefined

instance.my_property_name = '1';

console.log(instance.my_property_name);>>> "SET: 1"

Enforced properties

The function you use to define an enforced property isn't just a getter or a setter, it's both. It's an enforcer.

It's a function that will only be called upon each set, or as long as the current value it has is null/undefined.

An example will be more clear:

ExtendedClass.enforceProperty(function enforced_value(new_value, old_value) {

    if (new_value == null) {
        new_value = false;
    }

    return new_value
});

So what this enforcer will do is pretty simple: it will make sure the value is either false OR whatever you set it to, as long as that isn't null or undefined.

let instance = new ExtendedClass();
console.log(instance.enforced_value);>>> 1

instance.enforced_value = 2;
console.log(instance.enforced_value);>>> 2

instance.enforced_value = null;
console.log(instance.enforced_value);>>> 1

Constitutors

It's important to note that constitutors are executed only when the class is created or extended, not when an instance of the class is created. This means that the code inside the constitutor will only be executed once, when the class is first defined, and not every time an instance of the class is created

There are 2 main reasons why you would want to use this:

  • The function is executed on the class and all of its (future) children
  • The function is executed after all other classes have been created

For example:

ExtendedClass.constitute(function addSomething() {
    // `this` refers to the current class
    this.added_by_constitutor = 47;
});

So after alchemy has been loaded, this property will now be available on the class:

console.log(ExtendedClass.added_by_constitutor)>>> 47

Of course the really useful part is for child classes:

const MoreExtended = Function.inherits('SomewhereElse.ExtendedClass', 'MoreExtended');

At this point added_by_constitutor will not be available yet, since the constitutors are executed on the next tick.

console.log(MoreExtended.added_by_constitutor)>>> 47