[gjs] lang: Add new "Class" framework adapted from MooTools



commit bb7272c68628feb96991cb9751e041bd1d145c3b
Author: Jasper St. Pierre <jstpierre mecheye net>
Date:   Mon Oct 24 01:34:32 2011 -0400

    lang: Add new "Class" framework adapted from MooTools
    
    The current strategy of using __proto__ and _init to implement prototype chains
    is non-standard and error-prone. Use something a bit more automatic to help us
    along.
    
    Additionally, when patches come for inheriting from gi objects, we can use the
    existing framework to hoist ourselves without too much effort.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=662582

 Makefile-test.am     |    1 +
 modules/lang.js      |   80 +++++++++++++++++++++++++++++++++++++++++++
 test/js/testClass.js |   92 ++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 173 insertions(+), 0 deletions(-)
---
diff --git a/Makefile-test.am b/Makefile-test.am
index 55a05be..445a3de 100644
--- a/Makefile-test.am
+++ b/Makefile-test.am
@@ -175,6 +175,7 @@ EXTRA_DIST +=					\
 	test/js/testself.js			\
 	test/js/testByteArray.js		\
 	test/js/testCairo.js			\
+	test/js/testClass.js			\
 	test/js/testDBus.js			\
 	test/js/testGDBus.js			\
 	test/js/testEverythingBasic.js		\
diff --git a/modules/lang.js b/modules/lang.js
index 061468e..47bd7f2 100644
--- a/modules/lang.js
+++ b/modules/lang.js
@@ -128,5 +128,85 @@ function defineAccessorProperty(object, name, getter, setter) {
     object.__defineSetter__(name, setter);
 }
 
+// Class magic
+// Adapted from MooTools, MIT license
+// https://github.com/mootools/moootools-core
+
+function _Base() {
+}
+
+_Base.prototype.__name__ = '_Base';
+_Base.prototype.toString = function() {
+    return '[object ' + this.__name__ + ']';
+}
+
+function _parent() {
+    if (!this.__caller__)
+        throw new TypeError("The method 'parent' cannot be called");
+
+    let caller = this.__caller__;
+    let name = caller._name;
+    let parent = caller._owner.__super__;
+
+    let previous = parent ? parent.prototype[name] : undefined;
+
+    if (!previous)
+        throw new TypeError("The method '" + name + "' is not on the superclass");
+
+    return previous.apply(this, arguments);
+}
+
+function wrapFunction(obj, name, meth) {
+    if (meth._origin) meth = meth._origin;
+
+    function wrapper() {
+        this.__caller__ = wrapper;
+        let result = meth.apply(this, arguments);
+        this.__caller__ = null;
+        return result;
+    }
+
+    wrapper._origin = meth;
+    wrapper._name = name;
+    wrapper._owner = obj;
+
+    return wrapper;
+}
+
+function Class(params) {
+    if (!params.Name) {
+        throw new TypeError("Classes require an explicit 'name' parameter.");
+    }
+
+    let newClass = function() {
+        if (!this._init)
+            return this;
+
+        return this._init.apply(this, arguments);
+    };
+
+    let parent = params.Extends;
+    if (!parent)
+        parent = _Base;
+
+    newClass.__super__ = parent;
+    newClass.prototype = Object.create(parent.prototype);
+
+    for (let prop in params) {
+        let value = params[prop];
+
+        if (typeof value === 'function')
+            value = wrapFunction(newClass, prop, value);
+
+        newClass.prototype[prop] = value;
+    }
+
+    newClass.prototype.constructor = newClass;
+    newClass.prototype.__name__ = params.Name;
+    newClass.prototype.parent = _parent;
+
+    return newClass;
+}
+
 // Merge stuff defined in native code
 copyProperties(imports.langNative, this);
diff --git a/test/js/testClass.js b/test/js/testClass.js
new file mode 100644
index 0000000..efcea13
--- /dev/null
+++ b/test/js/testClass.js
@@ -0,0 +1,92 @@
+// application/javascript;version=1.8
+
+const Lang = imports.lang;
+
+function assertArrayEquals(expected, got) {
+    assertEquals(expected.length, got.length);
+    for (let i = 0; i < expected.length; i ++) {
+        assertEquals(expected[i], got[i]);
+    }
+}
+
+const MagicBase = new Lang.Class({
+    Name: 'MagicBase',
+
+    _init: function(a, buffer) {
+        if (buffer) buffer.push(a);
+        this.a = a;
+    },
+
+    foo: function(a, buffer) {
+        buffer.push(a);
+        return a * 3;
+    }
+});
+
+const Magic = new Lang.Class({
+    Name: 'Magic',
+
+    Extends: MagicBase,
+
+    _init: function(a, b, buffer) {
+        this.parent(a, buffer);
+        if (buffer) buffer.push(b);
+        this.b = b;
+    },
+
+    foo: function(a, b, buffer) {
+        let val = this.parent(a, buffer);
+        buffer.push(b);
+        return val * 2;
+    }
+});
+
+const ToStringOverride = new Lang.Class({
+    Name: 'ToStringOverride',
+
+    toString: function() {
+        let oldToString = this.parent();
+        return oldToString + '; hello';
+    }
+});
+
+function testClassFramework() {
+    let newMagic = new MagicBase('A');
+    assertEquals('A',  newMagic.a);
+}
+
+function testInheritance() {
+    let buffer = [];
+
+    let newMagic = new Magic('a', 'b', buffer);
+    assertArrayEquals(['a', 'b'], buffer);
+
+    buffer = [];
+    let val = newMagic.foo(10, 20, buffer);
+    assertArrayEquals([10, 20], buffer);
+    assertEquals(10*6, val);
+}
+
+function testConstructor() {
+    assertEquals(Magic, Magic.prototype.constructor);
+
+    let newMagic = new Magic();
+    assertEquals(Magic, newMagic.constructor);
+}
+
+function testInstanceOf() {
+    let newMagic = new Magic();
+
+    assertTrue(newMagic instanceof Magic);
+    assertTrue(newMagic instanceof MagicBase);
+}
+
+function testToString() {
+    let newMagic = new MagicBase();
+    assertEquals('[object MagicBase]', newMagic.toString());
+
+    let override = new ToStringOverride();
+    assertEquals('[object ToStringOverride]; hello', override.toString());
+}
+
+gjstestRun();



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]