UNPKG

esf-javascript-core

Version:

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

466 lines (395 loc) 16.2 kB
(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; } })); })();