@koga73/oop
v2.2.4
Published
This project when combined with design patterns adds common OOP functionality to JavaScript
Downloads
11
Maintainers
Readme
OOP JS
This project when combined with design patterns adds common OOP functionality to JavaScript
Goals:
- Provide OOP like functionality including namespacing, classes, inheritance, public / private scope
- Provide a cross-browser event model that can be added to any object
- Provide methods for cloning and extending objects
- Provide methods for type checking
Install:
npm i @koga73/oop
Run unit tests:
Run NodeJS unit test:
npm test
Run HTML/JS unit test:
tests/test.html
examples/pong snippet:
(function(){
//Imports from Pong example
var Models = Pong.Models;
var DomRenderer = Pong.Renderers.DomRenderer;
var InputManager = Pong.Managers.InputManager;
var NormalTimer = Utils.NormalTimer;
//This returns a reference to the class. We are using the word "_class" as a shortcut to easily access static members
var _class =
namespace("Foo.Bar.Pong",
construct({
//Constants
static:{
ID_BALL:"ball",
SPEED_BALL:400, //Pixels-per-second
singleton:null,
getSingleton:function(){
if (!_class.singleton){
_class.singleton = new Foo.Bar.Pong(
_class.ID_BALL
);
}
return _class.singleton;
}
},
//In general _public is optional since it's the same as "this" (except in the scope of a private method)
//Properties and methods starting with an underscore '_' are private
instance:function(_private, _public){
return {
ball:null,
normalTimer:null,
inputManager:null,
_renderer:null,
_renderQueue:[],
__construct:function(ballId){
//Init game objects
this.ball = new Models.Object2D(ballId);
//Init game timer
this.normalTimer = new NormalTimer({
paused:false,
onTick:_private._onTick
});
this.inputManager = InputManager.getSingleton();
//Init renderer
_private._renderer = new DomRenderer();
_private._renderQueue = [
this.ball
];
//--- LISTEN FOR EVENTS ---
//Start
this.resetBall();
},
//Delta is the time elapsed between ticks
//Multiplying a movement value by delta makes the movement timebased rather than based on the clock cycle
_onTick:function(delta){
//Move
var queueLen = _private._renderQueue.length;
for (var i = 0; i < queueLen; i++){
var obj2d = _private._renderQueue[i];
obj2d.x += obj2d.moveX * delta;
obj2d.y += obj2d.moveY * delta;
}
//--- DO LOGIC ---
//Do something with _class.SPEED_BALL
//Render
_private._renderer.render(_private._renderQueue);
},
resetBall:function(){
//--- DO LOGIC ---
}
};
}
}));
})();
Simple class:
OOP.namespace("foo.bar.Shape", OOP.construct({
instance:{
width:100,
height:200
}
}));
var defaultShape = new foo.bar.Shape();
console.log(defaultShape);
Change scope:
This allows you to change the scope of OOP methods so don't have to put "OOP" all over the place.
//Add OOP methods to window/global by default or to any object passed in
OOP.changeScope();
namespace("foo.bar.Shape", construct({
instance:{
width:100,
height:200
}
}));
var defaultShape = new foo.bar.Shape();
console.log(defaultShape);
Constructor:
OOP.namespace("foo.bar.Shape", OOP.construct({
instance:{
width:100,
height:200,
__construct:function(newWidth, newHeight){
this.width = newWidth || this.width;
this.height = newHeight || this.height;
console.log("This gets called when a new instance is created", this.width, this.height);
}
}
}));
//Note that if we pass in non-object parameters they will get sent to the constructor
//Object type parameters will apply value overrides to the instance
var customShape = new foo.bar.Shape(300, 400, {
test:"this prop gets added to the instance"
});
console.log(customShape);
Static:
OOP.namespace("foo.bar.Shape", OOP.construct({
instance:{
width:100,
height:200,
},
static:{
SOME_CONST:123,
getArea:function(obj){
return obj.width * obj.height;
}
}
}));
var instance = new foo.bar.Shape({
width:300,
height:400
});
console.log(foo.bar.Shape.SOME_CONST);
console.log(foo.bar.Shape.getArea(instance));
Public / Private scope:
OOP.namespace("foo.bar.Shape", OOP.construct({
instance:function(_private, _public){
return {
width:100,
height:200
_thisIsPrivate:"some message",
getThisIsPrivate:function(){
return _private._thisIsPrivate;
}
};
}
}));
var defaultShape = new foo.bar.Shape();
console.log(defaultShape);
Note that when a function is passed to "instance" a public and private scope is created. Anything starting with an underscore '_' is private. You can use the _private and _public references passed into the function to call between scopes. Optionally "this" refers to the _public or _private scope respectively. You can also use these _public and _private variables to avoid creating event delegates for "this".
Inheritance:
OOP.namespace("foo.bar.Shape", OOP.construct({
instance:{
width:100,
height:200
},
static:{
getArea:function(obj){
return obj.width * obj.height;
}
}
}));
OOP.namespace("foo.bar.Triangle", OOP.inherit(foo.bar.Shape, OOP.construct({
instance:{
width:300,
angles:[30, 60, 90]
},
static:{
getArea:function(obj){
return obj.width * obj.height * 0.5;
}
}
})));
var triangle = new foo.bar.Triangle();
console.log(OOP.isType(triangle, foo.bar.Triangle)); //true
console.log(OOP.isType(triangle, "foo.bar.Triangle")); //true
console.log(OOP.isType(triangle, foo.bar.Shape)); //true
console.log(triangle._interface); //Triangle instance
console.log(triangle._super); //Shape instance
console.log(triangle._super._interface); //Triangle instance
console.log(triangle._type); //"foo.bar.Triangle"
console.log(triangle._super._type); //"foo.bar.Shape"
Events:
OOP.namespace("foo.bar.Shape", OOP.construct({
instance:{
width:100,
height:200
},
events:true,
static:{
getArea:function(obj){
return obj.width * obj.height;
}
}
}));
var instance = new foo.bar.Shape();
instance.addEventListener("test-event", function(evt, data){
console.log("Got event", evt, data);
});
instance.dispatchEvent(new OOP.Event("test-event", 123));
Note that events fired from inherited classes (_super) will bubble up (they share the same _eventHandlers)
Add events to any object:
var myObj = {};
OOP.addEvents(myObj);
myObj.addEventListener("test-event", function(evt, data){
console.log("Got event", evt, data);
});
myObj.dispatchEvent(new OOP.Event("test-event", 123));
Clone:
var obj = OOP.clone({foo:{bar:"foobar"}}); //Makes a deep copy - The foo objects will be different
var obj = OOP.clone({foo:{bar:"foobar"}}, false); //Makes a shallow copy - The foo objects will be the same reference
Extend:
var foo = {abc:123};
var bar = {def:{ghi:456}};
//Extend bar onto foo - deep by default meaning foo.def will not equal bar.def
OOP.extend(foo, bar);
//Extend bar onto foo - shallow meaning foo.def and bar.def will be the same reference
OOP.extend(foo, false, bar);
//Extend bar onto foo - shallow meaning foo.def and bar.def will be the same reference but the third object is deep again
OOP.extend(foo, false, bar, true, {jkl:{mno:789}});
Note the number of arguments is unlimited. When a boolean is encountered it sets the "deep" flag for subsequent objects. The first object found in the arguments is what gets extended (you could pass true/false as the first argument).
Type checks:
OOP.isType
OOP.isFunction
OOP.isArray
OOP.isObject
OOP.isString
OOP.isBoolean
OOP.isRegExp
Full API:
init:_methods.init,
//Class
namespace:_methods.namespace,
inherit:_methods.inherit,
createClass:_methods.createClass,
construct:_methods.construct,
//Core
clone:_methods.clone,
extend:_methods.extend,
//Type checks
isType:_methods.isType,
isFunction:_methods.isFunction,
isArray:_methods.isArray,
isObject:_methods.isObject,
isString:_methods.isString,
isBoolean:_methods.isBoolean,
isRegExp:_methods.isRegExp,
//Events
Event:_methods.event,
addEvents:_methods.addEvents,
removeEvents:_methods.removeEvents,
addEventListener:_methods.addEventListener,
on:_methods.addEventListener, //Alias
removeEventListener:_methods.removeEventListener,
off:_methods.removeEventListener, //Alias
dispatchEvent:_methods.dispatchEvent,
trigger:_methods.dispatchEvent, //Alias
emit:_methods.dispatchEvent //Alias