@koga73/oop

2.2.4 • Public • Published

Build Status Support: IE8+ Dependencies: None License: MIT

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

Package Sidebar

Install

npm i @koga73/oop

Weekly Downloads

1

Version

2.2.4

License

MIT

Unpacked Size

78.7 kB

Total Files

20

Last publish

Collaborators

  • koga73