From 69f14fb21837c08452e1646d4089abe17b737d14 Mon Sep 17 00:00:00 2001 From: "alexis.ferreyra" Date: Mon, 29 Oct 2012 20:43:27 -0300 Subject: [PATCH] New methods: - Class.$append : allows extending existing class object with new methods - Class.$define : keeps a table of existing classes to allow forward declaration of types --- classy.js | 100 ++++++++++++++++++++++++++++++++++++++++---------- tests/core.js | 71 +++++++++++++++++++++++++++++++---- 2 files changed, 143 insertions(+), 28 deletions(-) diff --git a/classy.js b/classy.js index 9ca6051..4fb3443 100644 --- a/classy.js +++ b/classy.js @@ -67,6 +67,83 @@ /* what version of classy are we using? */ Class.$classyVersion = CLASSY_VERSION; + /* adds new methods to a class object */ + Class.$append = function(properties){ + Class.$include(this.constructor.prototype, this.$super_prototype, properties); + }; + + /* helper metod to copy functions inside properties object to class prototype */ + Class.$include = function(prototype, super_prototype, properties) { + /* copy all properties over to the new prototype */ + for (var name in properties) { + var value = getOwnProperty(properties, name); + if (name === '__include__' || + value === undefined) + continue; + + prototype[name] = typeof value === 'function' && usesSuper(value) ? + (function(meth, name) { + return function() { + var old_super = getOwnProperty(this, '$super'); + this.$super = super_prototype[name]; + try { + return meth.apply(this, arguments); + } + finally { + setOrUnset(this, '$super', old_super); + } + }; + })(value, name) : value + } + }; + + /* Allows forward declarations of classes. + @param {string} fullClassName String with the class full name. Make sure to provide always the same string for on class. + @param {Class} baseClass The base class object that will be used to create the new class if required. + @param {object} properties An object with properties to be defined as expected by method $extend. + @return {Class} Returns a new Class object or the existing one if it was defined before. + */ + Class.$define = (function(){ + var $registeredClasses = {}; + + function existsClass(classname) { + return $registeredClasses[classname] !== undefined; + } + function getClass(classname) { + return $registeredClasses[classname]; + } + function registerClass(classname, classObj) { + $registeredClasses[classname] = classObj; + } + + return function(fullClassName, baseClass, properties){ + if(baseClass !== undefined && typeof baseClass === "object" && properties === undefined) + { + properties = baseClass; + baseClass = undefined; + } + + if(properties === undefined || properties == null) properties = {}; + + if(existsClass(fullClassName)) + { + var classObj = getClass(fullClassName); + if(properties !== undefined && properties !== null) + { + classObj.$append(properties); + } + return classObj; + } + if(baseClass === undefined) + { + baseClass = Class; + } + + registerClass(fullClassName, baseClass.$extend(properties)); + return getClass(fullClassName); + }; + })(); + /* extend functionality */ Class.$extend = function(properties) { var super_prototype = this.prototype; @@ -97,26 +174,7 @@ } /* copy all properties over to the new prototype */ - for (var name in properties) { - var value = getOwnProperty(properties, name); - if (name === '__include__' || - value === undefined) - continue; - - prototype[name] = typeof value === 'function' && usesSuper(value) ? - (function(meth, name) { - return function() { - var old_super = getOwnProperty(this, '$super'); - this.$super = super_prototype[name]; - try { - return meth.apply(this, arguments); - } - finally { - setOrUnset(this, '$super', old_super); - } - }; - })(value, name) : value - } + Class.$include(prototype, super_prototype, properties); /* dummy constructor */ var rv = function() { @@ -140,6 +198,8 @@ return the class */ rv.prototype = prototype; rv.constructor = rv; + rv.$append = Class.$append; + rv.$super_prototype = super_prototype; rv.$extend = Class.$extend; rv.$withData = Class.$withData; return rv; diff --git a/tests/core.js b/tests/core.js index 8c25f22..65d5e6d 100644 --- a/tests/core.js +++ b/tests/core.js @@ -8,10 +8,10 @@ test('$noConflict unsets Class from window', function() { var Classy = Class.$noConflict(); - equals(typeof(Class), 'undefined', + equal(typeof(Class), 'undefined', '$noConflict unsets Class from window'); - same(Classy, original, + deepEqual(Classy, original, 'the returned Class is the same as the original'); // cleanup @@ -28,22 +28,22 @@ test('$super calls parent method', function() { var Englishman = Greeter.$extend({ greeting: function() { return 'Hello, ' + this.$super(); } }); - same((Spaniard().greeting()), 'Hola, Armin!', + deepEqual((Spaniard().greeting()), 'Hola, Armin!', 'Spanish greeting generated.'); - same((Englishman().greeting()), 'Hello, Armin!', + deepEqual((Englishman().greeting()), 'Hello, Armin!', 'English greeting generated.'); }); test('$extend exists and works', function() { - same(typeof(Class.$extend), 'function', + deepEqual(typeof(Class.$extend), 'function', 'basic class has $extend function'); var SubClass = Class.$extend({}); - same(typeof(SubClass.$extend), 'function', + deepEqual(typeof(SubClass.$extend), 'function', 'subclasses also receive $extend'); - same(SubClass.$extend, Class.$extend, + deepEqual(SubClass.$extend, Class.$extend, 'base class and subclass have same $extend function'); }); @@ -51,7 +51,7 @@ test('classes can be used with or without `new`.', function() { var pig1 = new Animal('pig'); var pig2 = Animal('pig'); - same(pig1, pig2, + deepEqual(pig1, pig2, 'Animal instances are the same when made with or without `new`'); }); @@ -220,3 +220,58 @@ test('class variable inheritance', function() { equal(SubSubTest.bar, SubTest.bar, 'SubSubTest.bar is Test.bar'); equal(SubSubTest.foo, 999, 'SubSubTest.foo has been overridden'); }); + +test('$define used as forward declaration', function(){ + + Base = Class.$define("Base"); + + Derived = Class.$define("Derived", Base, { + __init__: function(){ + this.$super(); + this._derivedNumber = 3; + }, + baseNumber: function(){ + return Base.prototype.number.call(this); + }, + derivedNumber: function(){ + return this._derivedNumber; + }, + }); + + + Base = Class.$define("Base", { + __init__: function(){ + this._number = 5; + }, + number: function(){ + return this._number; + }, + setNumber: function(value){ + this._number = value; + } + }); + + ok(Derived, 'Derived class declared before Base class.'); + ok(new Derived() !== undefined, 'Derived object was created'); + equal(new Derived().number(), 5, 'Method from base called.'); + +}); + +test('$append method used to extend a class.', function(){ + + Test = Class.$extend({ + __init__: function(){ + this._number = 10; + } + }); + + Test.$append({ + number: function(){ + return this._number; + }, + }); + + var obj = new Test(); + equal(obj.number(), 10, 'Test class was extended with new methods.'); + +});