UNPKG

iced-js

Version:

Interface and Class ExperimenteD JavaScript

498 lines (424 loc) 14.9 kB
# 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](https://github.com/brunoczim/iced-js/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: ```javascript //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`: ```javascript 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: ```javascript //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. ```javascript //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. ```javascript //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: ```javascript //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. ```javascript //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. ```javascript //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. ```javascript //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`. ```javascript //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. ```javascript //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. ```javascript //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. ```javascript //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. ```javascript //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. ```javascript //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. ```javascript 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. ```javascript //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: ```javascript //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. ```javascript 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. ```javascript //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({ }); ```