esf-javascript-core
Version:
EsFramework Javascript core. Contains simple classloader, heavily inspired by ExtJs
466 lines (395 loc) • 16.2 kB
JavaScript
(function () {
var global = this,
//Core class creation functionality
core = {
/**
* Create class (not instance)
* @param source
* @return
*/
create: function (source) {
var newClass = source.hasOwnProperty("constructor") ? source.constructor : function() {};
core.override(newClass, source);
return newClass;
},
/**
* Extend class
* @param superClass
* @param source
*/
extend : function(superClass, source) {
var newClass = source.hasOwnProperty("constructor") ? source.constructor : function() {
return superClass.apply(this, arguments);
};
// Save reference to superclass
newClass.superClass = superClass;
var superClone = function() {};
superClone.prototype = superClass.prototype;
newClass.prototype = new superClone();
newClass.prototype.constructor = newClass;
// Add callparent method
newClass.prototype.callParent = function () {
// <debug.error>
if (typeof(superClass.prototype[this.callParent.caller.$name]) == "undefined")
throw 'Parent method ('
+ this.callParent.caller.$name+') is not defined in ' + this.$name;
// <debug.error>
if (this.callParent.caller.$name == "constructor") {
return superClass.apply(this, arguments);
}
return superClass.prototype[this.callParent.caller.$name].apply(this, arguments);
};
if (source)
core.override(newClass, source);
return newClass;
},
/**
* Add attributes from source to prototype of scope
* the scope of this function should be the newly created class
* @private
* @param source
*/
override: function(target, source) {
// <debug.error>
if ( typeof (source) != "object" || !source)
throw new TypeError("Object needed as source.");
// </debug.error>
for (var property in source) {
if (source.hasOwnProperty(property)) {
// Add information about name to method
if (typeof source[property] == 'function') {
source[property].$name = property;
}
target.prototype[property] = source[property];
}
}
}
};
/**
* @singleton
*/
module.exports = new (core.create({
/**
* Instances of class definitions
* @private
*/
classes: {},
/**
* Class constructors for different number of args
* @private
*/
instantiators: [],
/**
* Tagged classes
*/
tags: {},
/**
* Singletons
*/
singletons: {},
/**
* Create instance of class
* @param name can be either class or name of a class
* @return {*}
*/
create: function (name) {
// Create instantiator
if (!this.instantiators[arguments.length]) {
var a = [];
for (i = 1; i < arguments.length; i++) {
a.push('a[' + i + ']');
}
this.instantiators[arguments.length] = new Function('cls', 'a', 'return new cls('+a.join(',')+')');
}
// <debug.error>
if (typeof(name) != "function") {
if (!(this.classes[name] || this.getClass(name)))
throw "Can not create undefined class " + name;
if (typeof(this.classes[name].loaded) != "undefined" || this.classes[name].loaded == false)
throw "Can not create class with unloaded dependencies";
}
// </debug.error>
return this.instantiators[arguments.length](typeof(name) == "function" ? name :
(this.classes[name] || this.getClass(name)), arguments);
},
/**
* Define class
* @param name
*/
define: function (name, source, options) {
// Parse args
if (typeof(name) == "object") { // Map args if private class
options = source;
source = name;
name = false; // name arg is boolean false if the definition is a private class
}
if (typeof(options) == "undefined")
options = {};
if (typeof(source) == "function")
source = source();
// Save importand options to keep code clean
var singleton = typeof(options.singleton) !="undefined" && options.singleton==true,
extend = typeof(options.extend) == "string",
cls;
// Create class
// TODO: add multiple class inheritance
if (extend) {
if (!this.getClass(options.extend))
throw new Error(
"Superclass `" + options.extend + "` of " + (name || ('private class')) + " not found"
);
cls = core.extend(this.getClass(options.extend), source);
} else {
cls = core.create(source)
}
// Save class name in prototype of class
if (name) {
cls.prototype.$name = name;
}
// Save that class is a singleton
if (singleton) {
cls.prototype.$singleton = true;
}
// Save that class extends herself (more specifically the already defined instance)
//<debug>
if (name && options.extend == name) {
cls.prototype.$overridden =
typeof(cls.prototype.$overridden=="undefined") ? 0
: ++cls.prototype.$overridden;
}
//<debug>
// Load deps if given
if (typeof(options.deps) != "undefined") {
// <debug.error>
if (!this.loader)
throw "Can not define class with deps without initialized loader";
// </debug.error>
// Dependency que. class is loaded if que is empty
var que = options.deps.slice(0);
cls.prototype.deps = [];
for (var k in options.deps) {
// Delegate loading dependency to loader
this.loader.require(options.deps[k], function (dep) {
// Store loaded dependency
cls.prototype.deps[options.deps[k].replace(/-/, '_')] = dep;
delete que[k];
// Store that all dependencies were loaded
if (que.length == 0) {
self.loader.emit('load', name);
}
});
}
} else if (this.loader) {
this.loader.emit('load', name);
}
// Create instance if singleton
if (singleton) {
// <debug.error>
if (!name)
throw new Error("Can not create singleton for private class");
// <debug.error>
if (this.loader) {
this.loader.on('load', function (n) {
if (n==name) {
self.singletons[name] = this.create(cls);
return false; // Stop searching
}
});
} else {
this.singletons[name] = this.create(cls);
}
// TODO: add debug function to notify about timeouts
}
// Map to global namespace
if (name) {
var split = name.split('.'),
ns = global;
for (var i = 0; i<split.length-1; i++) {
if (typeof(ns[split[i]]) == "undefined")
ns[split[i]] = {};
ns = ns[split[i]];
}
ns[split[split.length-1]] = singleton ? this.singletons[name] : cls;
}
// Save tags if given
if (typeof(options.tags) != "undefined") {
for (var k in options.tags) {
// Create array for name
if (typeof(this.tags[options.tags[k]]) == "undefined")
this.tags[options.tags[k]] = [];
// Save tag
this.tags[options.tags[k]].push(
singleton ? this.singletons[name] : cls
);
}
}
// Save defined class
if (name) {
this.classes[name] = cls;
}
// Return
if (!name) {
return cls; // private class definition, no method chainging return prototype instead
}
return this;
},
/**
* Apply given object attributes to class (mostly singleton rather than prototype)
* @param deep {Boolean} optional
* @param {String} target
* @param {Object} object1
*/
apply: function (target) {
// Get class for target if string is given
if (typeof(target) == "string") {
target = this.isSingleton(target) ? this.getSingleton(target) : this.getClass(target);
}
/*
* excerpt from jquery source code (see http://code.jquery.com/jquery-latest.js)
*/
/**
* Copyright 2012 jQuery Foundation and other contributors
* http://jquery.com/
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
var options, name, src, copy, copyIsArray, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
// Handle a deep copy situation
if ( typeof target === "boolean" ) {
deep = target;
target = arguments[1] || {};
// skip the boolean and the target
i = 2;
}
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
target = {};
}
// extend jQuery itself if only one argument is passed
if ( length === i ) {
target = this;
--i;
}
for ( ; i < length; i++ ) {
// Only deal with non-null/undefined values
if ( (options = arguments[ i ]) != null ) {
// Extend the base object
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
// Prevent never-ending loop
if ( target === copy ) {
continue;
}
// Recurse if we're merging plain objects or arrays
if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
if ( copyIsArray ) {
copyIsArray = false;
clone = src && jQuery.isArray(src) ? src : [];
} else {
clone = src && jQuery.isPlainObject(src) ? src : {};
}
// Never move original objects, clone them
target[ name ] = jQuery.extend( deep, clone, copy );
// Don't bring in undefined values
} else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
return this;
},
/**
* Find tagged classes
* @param tag
* @return {Object}
*/
require: false,
/**
* Find tagged classes
* @param tag
* @return {Array}
*/
findTaggedClasses: function (tag) {
return typeof(this.tags[tag]) == "undefined" ? [] : this.tags[tag];
},
/**
* Alias class method
* @param cls
* @param method
* @return {Function}
*/
alias: function (cls, method) {
if (typeof(cls) == "string") {
cls = this.getSingleton(cls);
if (!cls)
throw "Class (" + cls + ") not found";
}
return function () {
cls[method].apply(cls, arguments);
}
},
/**
* Returns wether class for given name is a singleton
* @param name
* @return {*}
*/
isSingleton: function (name) {
var cls = this.getClass(name);
return cls && cls.prototype.singleton;
},
/**
* Get singelton
* @param name
* @return {Boolean}
*/
getSingleton: function (name) {
return typeof(this.singletons[name]) == "undefined" ? false : this.singletons[name];
},
/**
* Get class prototype (not instance)
* @param name
* @return {*}
*/
getClass: function (name) {
// Esf internal classes
if (typeof(this.classes[name]) != "undefined")
return this.classes[name];
// Search in global ns
var split = name.split('.'),
ns = global;
for (var i = 0; i<split.length-1; i++) {
if (typeof(ns[split[i]]) == "undefined")
return false;
ns = ns[split[i]];
}
if (typeof(ns[split[split.length-1]]) == "function") {
return this.classes[name] = ns[split[split.length-1]]; // from global ns
}
// No class found
return false;
}
}));
})();