nest-parrot
Version:
Parrot built on react
281 lines (240 loc) • 9.69 kB
JavaScript
/*
* JSFace Object Oriented Programming Library
* https://github.com/tnhu/jsface
*
* Copyright (c) Tan Nhu
* Licensed under MIT license (https://github.com/tnhu/jsface/blob/master/LICENSE.txt)
*/
(function(context, OBJECT, NUMBER, LENGTH, toString, readyFns, readyCount, undefined, oldClass, jsface) {
/**
* Return a map itself or null. A map is a set of { key: value }
* @param obj object to be checked
* @return obj itself as a map or false
*/
function mapOrNil(obj) { return (obj && typeof obj === OBJECT && !(typeof obj.length === NUMBER && !(obj.propertyIsEnumerable(LENGTH))) && obj) || null; }
/**
* Return an array itself or null
* @param obj object to be checked
* @return obj itself as an array or null
*/
function arrayOrNil(obj) { return (obj && typeof obj === OBJECT && typeof obj.length === NUMBER && !(obj.propertyIsEnumerable(LENGTH)) && obj) || null; }
/**
* Return a function itself or null
* @param obj object to be checked
* @return obj itself as a function or null
*/
function functionOrNil(obj) { return (obj && typeof obj === "function" && obj) || null; }
/**
* Return a class itself or null
* @param obj object to be checked
* @return obj itself as a class or false
*/
function classOrNil(obj) { return (functionOrNil(obj) && (obj.prototype && obj === obj.prototype.constructor) && obj) || null; }
/**
* Util for extend() to copy a map of { key:value } to an object
* @param key key
* @param value value
* @param ignoredKeys ignored keys
* @param object object
* @param iClass true if object is a class
* @param oPrototype object prototype
*/
function copier(key, value, ignoredKeys, object, iClass, oPrototype) {
if ( !ignoredKeys || !ignoredKeys.hasOwnProperty(key)) {
object[key] = value;
}
}
/**
* Extend object from subject, ignore properties in ignoredKeys
* @param object the child
* @param subject the parent
* @param ignoredKeys (optional) keys should not be copied to child
*/
function extend(object, subject, ignoredKeys) {
if (arrayOrNil(subject)) {
for (var len = subject.length; --len >= 0;) { extend(object, subject[len], ignoredKeys); }
} else {
ignoredKeys = ignoredKeys || { constructor: 1, $super: 1, prototype: 1, $superp: 1 };
var iClass = classOrNil(object),
isSubClass = classOrNil(subject),
oPrototype = object.prototype, supez, key, proto;
// copy static properties and prototype.* to object
if (mapOrNil(subject) || iClass) {
for (key in subject) {
copier(key, subject[key], ignoredKeys, object, iClass, oPrototype);
}
}
if (isSubClass) {
proto = subject.prototype;
for (key in proto) {
copier(key, proto[key], ignoredKeys, object, iClass, oPrototype);
}
}
// prototype properties
if (iClass && isSubClass) { extend(oPrototype, subject.prototype, ignoredKeys); }
}
}
/**
* To make object fully immutable, freeze each object inside it.
* @param object to deep freeze
*/
function deepFreeze(object) {
var prop, propKey;
Object.freeze(object); // first freeze the object
for (propKey in object) {
prop = object[propKey];
if (!object.hasOwnProperty(propKey) || !(typeof prop === 'object') || Object.isFrozen(prop)) {
// If the object is on the prototype, not an object, or is already frozen,
// skip it. Note that this might leave an unfrozen reference somewhere in the
// object if there is an already frozen object containing an unfrozen object.
continue;
}
deepFreeze(prop); // recursively call deepFreeze
}
}
/**
* Create a class.
* @param parent parent class(es)
* @param api class api
* @return class
*/
function Class(parent, api) {
if ( !api) {
parent = (api = parent, 0); // !api means there's no parent
}
// TODO remove $statics, use $static instead
var clazz, constructor, singleton, statics, key, bindTo, len, i = 0, p,
ignoredKeys = { constructor: 1, $singleton: 1, $static:1, $statics: 1, prototype: 1, $super: 1, $superp: 1, main: 1, toString: 0 },
plugins = Class.plugins,
rootParent, parentClass, Stub;
api = (typeof api === "function" ? api() : api) || {}; // execute api if it's a function
constructor = api.hasOwnProperty("constructor") ? api.constructor : 0; // hasOwnProperty is a must, constructor is special
singleton = api.$singleton;
statics = api.$statics || api.$static;
// add plugins' keys into ignoredKeys
for (key in plugins) { ignoredKeys[key] = 1; }
// construct constructor
clazz = singleton ? function(){} : (constructor ? constructor : function(){});
// make sure parent is always an array
parent = !parent || parent instanceof Array ? parent : [ parent ];
len = parent && parent.length;
rootParent = parent[0];
if ( !singleton && len) {
parentClass = rootParent.prototype && rootParent === rootParent.prototype.constructor && rootParent;
if ( !parentClass) {
clazz.prototype = rootParent;
} else {
// Constributed by Freddy Snijder (https://github.com/tnhu/jsface/issues/26)
Stub = function(){};
Stub.prototype = parentClass.prototype;
Stub.prototype.constructor = Stub;
clazz.prototype = new Stub();
clazz.prototype.constructor = clazz; // restoring proper constructor for child class
parentClass.prototype.constructor = parentClass; // restoring proper constructor for parent class
}
}
// determine bindTo: where api should be bound
bindTo = singleton ? clazz : clazz.prototype;
// do inherit static properties and extentions (parents other than the first one)
while (i < len) {
p = parent[i++];
// copy static properties
for (key in p) {
if ( !ignoredKeys[key]) {
clazz[key] = p[key];
}
}
if ( !singleton && i !== 0) {
for (key in p.prototype) {
if ( !ignoredKeys[key]) {
bindTo[key] = p.prototype[key];
}
}
}
}
// copy properties from api to bindTo
for (key in api) {
if ( !ignoredKeys[key]) {
var prop = api[key];
if (prop && (prop.get || prop.set)) { // check if it is a property descriptor
prop.enumerable = true;
Object.defineProperty(bindTo, key, prop);
} else {
bindTo[key] = prop;
}
}
}
// copy static properties from statics to clazz
for (key in statics) {
clazz[key] = statics[key];
}
// add $super and $superp to refer to parent class and parent.prototype (if applied)
p = parent && rootParent || parent;
clazz.$super = p;
clazz.$superp = p && p.prototype || p;
for (key in plugins) { plugins[key](clazz, parent, api); } // pass control to plugins
if (typeof api.main === "function") { api.main.call(clazz, clazz); } // execute main()
return clazz;
}
/* Class plugins repository */
Class.plugins = {
$ready: function invoke(clazz, parent, api, loop) {
var r = api.$ready,
len = parent ? parent.length : 0,
count = len,
_super = len && parent[0].$super,
pa, i, entry;
// find and invoke $ready from parent(s)
while (len--) {
for (i = 0; i < readyCount; i++) {
entry = readyFns[i];
pa = parent[len];
if (pa === entry[0]) {
entry[1].call(pa, clazz, parent, api);
count--;
}
if ( !count) { break; }
}
}
// call $ready from grandparent(s), if any
if (_super) {
invoke(clazz, [ _super ], api, true);
}
// in an environment where there are a lot of class creating/removing (rarely)
// this implementation might cause a leak (saving pointers to clazz and $ready)
if ( !loop && functionOrNil(r)) {
r.call(clazz, clazz, parent, api); // invoke ready from current class
readyFns.push([ clazz, r ]);
readyCount++;
}
},
$const: function (clazz, parent, api) {
var key,
consts = api.$const;
// copy immutable properties from consts to clazz and freeze them recursively
for (key in consts) {
Object.defineProperty(clazz, key, { enumerable: true, value: consts[key] }); // enumerable for proper inheritance
if ((typeof clazz[key] === 'object') && !Object.isFrozen(clazz[key])) {
deepFreeze(clazz[key]); // if property is an unfrozen object, freeze it recursively
}
}
}
};
/* Initialization */
jsface = {
Class : Class,
extend : extend,
mapOrNil : mapOrNil,
arrayOrNil : arrayOrNil,
functionOrNil: functionOrNil,
classOrNil : classOrNil
};
if (typeof module !== "undefined" && module.exports) { // NodeJS/CommonJS
module.exports = jsface;
} else {
oldClass = context.Class; // save current Class namespace
context.Class = Class; // bind Class and jsface to global scope
context.jsface = jsface;
jsface.noConflict = function() { context.Class = oldClass; }; // no conflict
}
})(this, "object", "number", "length", Object.prototype.toString, [], 0);