iced-js

1.1.3 • Public • Published

iced-js

Introduction

ICED JS (Interface and Class ExperimenteD JavaScript) is a framework to deal with class-based OOP in JavaScript. ICED JS has members' visibilities, abstract classes and methodes, static members, and interfaces. Please, visit the wiki

Class Declaring

To create a class is very simple. You have to call the function iced.class() and pass an object that contains the class model. Just do something like:

//comment the line below for client-side JS
var iced = require("./node_modules/iced-js");
 
var MyClass = iced.class({
 
});

Creating instances

And then, we can create objects from the class MyClass with the static method new:

var myObject = MyClass.new();

Member declaring

To define a class member, you have to put it inside a property named according to the visibility. The visibility is an object itself, containing other objects inside. These objects inside the visibility will be describing the members:

//comment the line below for client-side JS
var iced = require("./node_modules/iced-js");
 
var MyClass = iced.class({
    public:{
        myPublicMemberOne:{ type: String },
        myPublicMemberTwo:{ type: Number, value: 5 }
    },
    private:{
        myPrivateMemberOne:{ type:Boolean, value:true },
        myPrivateMemberTwo:{ type: Object },
        myPrivateMemberOfMyOwnKind: { type: MyClass }
    },
    protected:{
        myProtectedMember:{ type: Object }
    }
});
 
var myObject = MyClass.new();
 
//will output 5
console.log(myObject.myPublicMemberTwo);
//will output undefined
console.log(myObject.myPrivateMemberOne);

The member-describing object has basically two main properties. The type property, which accepts ICED JS classes and the natives JavaScript types. If you declare the type as null or undefined, the member will accept any value. And finally, the value property receives the default value for the member. If no value is passed, ICED JS will assign it to the default type property, which for Number is 0, for String is '', for Object is {}, and etc.

Methods

To create methods, simply declare the type of a member as Function. You can refer to the object itself with the this pointer.

//comment the line below for client-side JS
var iced = require("./node_modules/iced-js");
 
var MyClass = iced.class({
    public:{
        getHidden:{ type: Function, value:function(){
            return this.myHiddenProperty;
        }}
    },
    private:{
        myHiddenProperty:{ type:String, value:"Hello" }
    }
});
 
var obj = MyClass.new();
 
//will output "Hello"
console.log(obj.getHidden());
Arguments Validation

ICED JS can validate methods' arguments. Just specify them with the args member-describing property. It must be an array, containing objects, each object is an arg.If it has the required property, it will be required, and if it has the optional property, it will be optional. Assign this property to the arg's type. If the type is declared as null, it will accept any type. It is not mandatory to declare the arguments.

//comment the line below for client-side JS
var iced = require("./node_modules/iced-js");
 
var Dog = iced.class({
    public:{
        bark:{
            type:Function,
            args:[
                //arg 1 will be required and as a String
                { required:String },
                //arg 2 won't be required, but if passed, must be a number
                { optional: Number },
                //arg 3 won't be required, and if passed, no type will be charged.
                { optional: null }
            ],
            value: function(sound, times, garbage){
                times = times || 1;
                for(var i = 0; i < times; i++){
                    console.log(sound);
                }
                if(garbage !== undefined){
                    console.log("I've found this:",garbage);
                }
            }
        }
    }
});
 

Note that both required and optional args can have null types. Note also that after one argument was declared optional, all the arguments that come after this optional one will be optional too.

Constructor Method

ICED JS also supports constructor method, that is called when the instance is created. Just declare a method with constructor name:

//comment the line below for client-side JS
var iced = require("./node_modules/iced-js");
 
var Dog = iced.class({
    public:{
        constructor:{ type:Function, value:function(){
            console.log("I'm alive, woof woof !");
        }}
    }
});
 
//will output "I'm alive, woof woof !"
var mydog = Dog.new();

Static members

To declare static members, just add a property static to the member-describing object, and assign it to true. Non-static members are auto declared as false to static.

//comment the line below for client-side JS
var iced = require("./node_modules/iced-js");
 
var MyCoolClass = iced.class({
   public:{
       staticMemberOne:{ static:true, type: Number, value: 19 },
       myNormalMemberOne:{ type: Number }
   }
});
 
//will output 19
console.log(MyCoolClass.staticMemberOne);

To refer to statics member of your own class, you can also use the keyword this. Static members are shared with the class's instance.

//comment the line below for client-side JS
var iced = require("./node_modules/iced-js");
 
var Ball = iced.class({
    public:{
        constructor:{ type:Function, value:function(){
            this.ballCount++;
        }},
        getBallCount:{ type:Function, static:true, value:function(){
            return this.ballCount;
        }}
    },
    private:{
        ballCount:{ static:true, type: Number }
    }
});
 
//will output 0
console.log(Ball.getBallCount());
 
var myball = Ball.new();
var anotherball = Ball.new();
 
//will output 2
console.log(Ball.getBallCount());
//will output 2
console.log(myball.getBallCount());
//will output 2
console.log(anotherball.getBallCount());

Inheritance

ICED JS supports inheritance. To make a class to be child of another, just set the parent property at the declaration. ICED JS supports the protected members, which are visible to the class itself and to its children classes.

//comment the line below for client-side JS
var iced = require("./node_modules/iced-js");
 
var SuperClass = iced.class({
    public:{
        visibleForEveryOneProperty:{ type: Number, value: 42 }
    },
    private:{
        visibileForNoOneProperty:{ type: String, value: "secret" }
    },
    protected:{
        visibileForMyChildrenProperty:{ type: String, value:"my children see this." }
    }
});
 
var SubClass = iced.class({
    parent:SuperClass,
    public:{
        getInherited:{ type: Function, value: function(){
            return this.visibileForMyChildrenProperty;
        }}
    }
});
 
var child = SubClass.new();
 
//will output "my children see this."
console.log(child.getInherited());

Member overriding

You can override members, this means that if a parent class has a member named 'something', the child class will be able to declare a member with the same name, and both members will still exists. You can access the parent member with a property named parent.

//comment the line below for client-side JS
var iced = require("./node_modules/iced-js");
 
var Super = iced.class({
    public:{
        getProp:{ type:Function, value:function(){
            return this.prop;
        }}
    },
    protected:{
        prop:{ type: Number, value:7 }
    }
});
 
var Sub = iced.class({
    parent:Super,
    public:{
        getNewProp:{ type:Function, value:function(){
            return this.prop;
        }}
    },
    private:{
        prop:{ type: String, value: "new prop value" }
    }
});
 
var instance = Sub.new();
//will output 7
console.log(instance.getProp());
//will output "new prop value"
console.log(instance.getNewProp());

Constructors can be overriden, but the previous constructor won't be called, unless you call them.

//comment the line below for client-side JS
var iced = require("./node_modules/iced-js");
 
var Super = iced.class({
    public:{
        constructor:{ type:Function, value:function(){
            console.log("parent created");
        }}
    }
});
 
var Sub = iced.class({
    parent:Super,
    public:{
        constructor:{ type:Function, value:function(){
            console.log("child created");
            this.parent.constructor();
        }}
    }
});
 
//will output "child created"
//and then "parent created"
var instance = Sub.new();

Virtual Members

Sometimes, using the this keyword inside a parent's member will bring some trouble up. If you'd like to call a child's member that overroded one of parent's member, it won't work; the parent member will be called. To be able to call the child member, you will have to declare the parent's member as virtual. Only protected and public members can be virtual. Static members can be also virtual.

This applies only to the inner scope of a class, outside world will be able to access only the member that overrode another, and not the overridden one.

//comment the line below for client-side JS
var iced = require("./iced-js");
 
var Parent = iced.class({
    public:{
        test:{type:Function, value:function(){
            console.log(this.hidden);
        }}
    },
    protected:{
        hidden:{type:Number, value: 123, virtual:true }
    }
});
 
var Son = iced.class({
    parent:Parent,
    protected:{
        hidden:{type:Number, value: 124 }
    }
});
 
//will print 124
Son.new().test();

Abstract Members and Abstract Classes

Abstract members are functions that will be implemented by children classes, and not by the parent. Declare them is similiar to declare static members; just use the abstract property and assign it to true. Abstract functions must be implemented with the same arguments that were declared. You can only add optional arguments.

Abstract functions must be implemented with the same staticness. Must also be implemented with a not more opened visibility. This Means that if it is declared as public, can be implemented with any visibility, but if declared as protected, can be only implemented as protected or private. Note that abstract methods must be only public or private. Abstract methods are automatically virtual.

//comment the line below for client-side JS
var iced = require("./node_modules/iced-js");
 
var Animal = iced.class({
    public:{
        makeNoise:{ value: Function, abstract: true, args:[{required:String}] }
    }
});
 
var Dog = iced.class({
    parent: Animal,
    public:{
        makeNoise:{ value: Function, args:[{required:String}], value:function(sound){
            console.log(sound);
        }}
    }
});
 
var Cat = iced.class({
    parent: Animal,
    public:{
        makeNoise:{ value: Function, args:[{required:String},{optional:Boolean}], value:function(sound, repeat){
            console.log(sound);
            if(repeat){
                console.log(sound);
            }
        }}
    }
});

As abstract members exists, abstract classes also do. Abstract classes are classes that can't be instanced, but can be normally inherited. To declare a class as abstract, just add the abstract property in its declaration and assign it to true.

//comment the line below for client-side JS
var iced = require("./node_modules/iced-js");
 
var Animal = iced.class({
    public:{
        makeNoise:{ value: Function, abstract: true, args:[{required:String}] }
    }
});
 
var Dog = iced.class({
    parent: Animal,
    public:{
        makeNoise:{ value: Function, args:[{required:String}], value:function(sound){
            console.log(sound);
        }}
    }
});
 
try{
    //will throw an error
    var myanimal = Animal.new();
} catch(e) {
 
}
 
try{
    //everything will be fine
    var mydog = Dog.new();
} catch(e) {
 
}
 

Interfaces

Interfaces can be compared to an abstract class with only abstract methods, with the only exception that a class can implement a lot of interfaces, but inherit from only one class. ICE JS supports interfaces. To declare an interface, just use the iced.interface function. Its declaration is similar to a class declaration.

//comment the line below for client-side JS
var iced = require("./node_modules/iced-js");
 
var myInterface = iced.interface({
    public:{
        doSomething:{ type: Function }
    }
});
 
var anotherInterface = iced.interface({
    private:{
        makeThings:{ type: Function, static: true}
    }
});

A class can implement multiple interfaces. To declare a interface implementation, add a property roles in the class declaration. roles must be an array containing all the interfaces implemented.

var MyClass = iced.class({
    roles:[myInterface, anotherInterface],
    public:{
        doSomething:{ type: Function, value: function(){
            console.log("something");
        }},
        makeThings:{ type: Function, static: true, function(){
            console.log("crazy stuff");
        }}
    }
});

Type Generalization

When declaring types, you can declare a superclass as a type, and then the variable will accept any value that is an instance of the superclass, or any instance of subclasses. And you can do this also with interfaces; you can declare a variable of type as an interface, and it will accept instances from any class that implement the interface. The interface type accepts null as a value, and it is its default value.

//comment the line below for client-side JS
var iced = require("./node_modules/iced-js");
 
var Animal = iced.class({
    public:{
        die:{ type:Function, value:function(){
            this.life = false;
        }}
    },
    private:{
        life:{ type: Boolean, value: true }
    }
});
 
var Rabbit = iced.class({
    parent:Animal
});
 
var Hunter = iced.interface({
    public:{
        hunt:{ type: Function, args:[{required:Animal}] }
    }
});
 
var Dog = iced.class({
    parent:Animal,
    roles:[Hunter],
    public:{
        hunt:{ type: Function, args:[{required:Animal}], value:function(animal){
            animal.die();
            return animal;
        }}
    }
});
 
var Camping = iced.class({
    public:{
        hunter:{ type:Hunter }
    }
});
 
var rabbit = Rabbit.new();
var camping = Camping.new();
camping.hunter = Dog.new();
console.log(camping.hunter.hunt(rabbit));

Namespaces

Imagine that, then you have a lot of classes. But you need to organize them, according to their sources. This is why you need namespaces. Namespaces are just like 'folders' for classes. A namespace is an object that has classes as members. There are two ways of creating namespaces in iced-js.

Way 1 - Creating a class and adding prepared classes as members

First, you prepare all your classes:

//comment the line below for client-side JS
var iced = require("./node_modules/iced-js");
 
var MyClass = iced.class({
 
});
 
var MyOtherClass = iced.class({
 
});

Now you create the namespace as a class, and declare the prepared classes as static members.

 
var myclasses = iced.class({
    public:{
        MyClass:{ type:Object, static:true, value: MyClass },
        MyOtherClass:{ type:Object, static:true, value: MyOtherClass }
    }
});

Way 2 - Creating a simple object and adding classes as properties

Create an object to be a namespace, and then just go declaring its properties as classes.

//comment the line below for client-side JS
var iced = require("./node_modules/iced-js");
 
var myclasses = {};
 
myclasses.MyClass = iced.class({
 
});
 
myclasses.MyOtherClass = iced.class({
 
});

Readme

Keywords

Package Sidebar

Install

npm i iced-js

Weekly Downloads

1

Version

1.1.3

License

ISC

Last publish

Collaborators

  • brunoczim