UNPKG

esf-core

Version:

EsFramework Javascript core. Contains simple classloader, heavily inspired by ExtJs

592 lines (513 loc) 21.5 kB
(function () { var global = this, //Core class creation functionality core = { /** * Create class (not instance) * @param source * @return */ create: function (source) { var newClass = this.getConstructor(source); core.override(newClass, source); return newClass; }, /** * Extend class * @param superClass * @param source */ extend : function(superClass, source) { var newClass = this.getConstructor( source, superClass.prototype.constructor ); var superClone = function() {}; superClone.prototype = superClass.prototype; newClass.prototype = new superClone(); newClass.prototype.constructor = source.hasOwnProperty("constructor") ? source.constructor : function () {}; // Save reference to superclass newClass.prototype.superClass = superClass; // Add callparent method newClass.prototype.callParent = function () { // Search which child class is calling this method var superClass = newClass; while (superClass.prototype[this.callParent.caller.$name] !== this.callParent.caller) { superClass = superClass.prototype.superClass; } superClass = superClass.prototype.superClass; // <debug.error> if (typeof(superClass) == "undefined") { var callingClass = newClass; while (callingClass.prototype[this.callParent.caller.$name] !== this.callParent.caller) { callingClass = callingClass.prototype.superClass; } throw "Parent method (" + this.callParent.caller.$name+") cant be called from " + callingClass.prototype.$name + " because it hasn't a mother class"; } 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; }, /** * Get the constructor of a class * @param source object that describes the class * @param defaultConstructor normally the constructor of the superclass that * will be used if the source doesn't have one * @return {Function} */ getConstructor: function (source, defaultConstructor) { return function() { // Clone instance attributes for (var property in this) { if (typeof(this[property]) == 'object') { this[property] = apply(true, source[property] instanceof Array ? [] : {}, this[property]); } } if (source.hasOwnProperty("constructor")) return source.constructor.apply(this, arguments); else if (typeof(defaultConstructor) != "undefined") { return defaultConstructor.apply(this, arguments); } return this; }; }, /** * 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]; } } } }, /* * 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. */ apply = function () { // TODO: make faster and (re)move some vars var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, toString = Object.prototype.toString, hasOwn = Object.prototype.hasOwnProperty, push = Array.prototype.push, slice = Array.prototype.slice, trim = String.prototype.trim, indexOf = Array.prototype.indexOf, class2type = { "[object Boolean]": "boolean", "[object Number]": "number", "[object String]": "string", "[object Function]": "function", "[object Array]": "array", "[object Date]": "date", "[object RegExp]": "regexp", "[object Object]": "object" }, jQuery = { isFunction: function (obj) { return jQuery.type(obj) === "function" }, isArray: Array.isArray || function (obj) { return jQuery.type(obj) === "array" }, isWindow: function (obj) { return obj != null && obj == obj.window }, isNumeric: function (obj) { return !isNaN(parseFloat(obj)) && isFinite(obj) }, type: function (obj) { return obj == null ? String(obj) : class2type[toString.call(obj)] || "object" }, isPlainObject: function (obj) { if (!obj || jQuery.type(obj) !== "object" || obj.nodeType) { return false } try { if (obj.constructor && !hasOwn.call(obj, "constructor") && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) { return false } } catch (e) { return false } var key; for (key in obj) {} return key === undefined || hasOwn.call(obj, key) } }; if (typeof target === "boolean") { deep = target; target = arguments[1] || {}; i = 2; } if (typeof target !== "object" && !jQuery.isFunction(target)) { target = {} } if (length === i) { target = this; --i; } for (i; i < length; i++) { if ((options = arguments[i]) != null) { for (name in options) { src = target[name]; copy = options[name]; if (target === copy) { continue } 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 : {}; } // WARNING: RECURSION target[name] = apply(deep, clone, copy); } else if (copy !== undefined) { target[name] = copy; } } } } return target; }; /** * @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: {}, /** * Tag filter */ tagFilter: {}, /** * Namespace of the object */ _ns: 'Esf', /** * 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, self = this, args = arguments; // Create class // TODO: add multiple class inheritance if (extend) { if (!this.getClass(options.extend)) { // <debug> if (!this.Loader || !(this.Loader.hasClass(options.extend) && name)) { // Can not load private class asynchronous throw new Error( "Superclass `" + options.extend + "` of " + (name || ('private class')) + " not found" ); } // <debug> // Load class asynchronous this.Loader.require(options.extend, function () { // Try loading mother class asynchronous // Class will be available if all dependencies were fetched self.define.apply(self, args); }); } 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> cls.prototype.deps = {}; this.Loader.npm.require(options.deps, cls.prototype.deps); } // 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 or self if (name) { var split = name.split('.'), ns = global; // If class is in the namespace of the core map it there instead of global if (split[0] == this._ns) { split.shift(); ns = this; } // TODO: Only map to global namespace if configured 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) { var tagName; // Parse format of the tag if (typeof(options.tags[k]) == "object") { tagName = options.tags[k].name; var hash = require('es-hash')(options.tags[k]); // Create array for filter if (typeof(this.tagFilter[hash]) == "undefined") this.tagFilter[hash] = []; // Save filter this.tagFilter[hash].push( singleton ? this.singletons[name] : cls ); } else { tagName = options.tags[k]; } // Create array for name if (typeof(this.tags[tagName]) == "undefined") this.tags[tagName] = []; // Save tag this.tags[tagName].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 () { return apply.apply(this, arguments); }, /** * Find tagged classes * @param tagName * @return {Array} */ findTaggedClasses: function (tagName, filter) { if (typeof(filter) != "undefined") { filter.name = tagName; var hash = require('es-hash')(filter); return typeof(this.tagFilter[hash]) == "undefined" ? [] : this.tagFilter[hash]; } return typeof(this.tags[tagName]) == "undefined" ? [] : this.tags[tagName]; }, /** * 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") { ns = {}; break; } ns = ns[split[i]]; } if (typeof(ns[split[split.length-1]]) == "function") { return this.classes[name] = ns[split[split.length-1]]; // from global ns } // Load class synchronous if defined in autoload // <debug> if (this.Loader && this.Loader.isAutoloaded(name)) { this.Loader.require(name); if (typeof(this.classes[name]) != "undefined") return this.classes[name]; } // /<debug> // No class found return false; }, Loader: false, globalize: function () { global.Esf = this; } })); })();