From c35598ca2b73e2522f939f4cadf88ecaabbe5a5a Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Thu, 11 Oct 2007 05:02:07 +0000 Subject: [PATCH] prototype: Clean up the new class API. --- CHANGELOG | 5 +++ src/base.js | 40 ++++++++++---------- test/unit/base.html | 89 ++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 109 insertions(+), 25 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5a3b9e2..88da399 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,10 @@ *SVN* +* Clean up the new class API. [sam, Tobie Langel] + - Add Class#addMethods for adding instance methods to classes. + - Remove Class.extend and Class.mixin. + - Class.create now takes a variable number of arguments: if the first argument is a class, the newly created class inherits from that class; all other arguments are treated as successive calls to addMethods. + * Fix contentloaded event initialization in IE. Closes #9457, #9488, #9707. [Mislav Marohnić] * Deprecate document.getElementsByClassName and Element#getElementsByClassName since native versions return a NodeList and we can only return an Array. Please use $$ or Element#select instead. [sam] diff --git a/src/base.js b/src/base.js index 3be7c8e..638ab84 100644 --- a/src/base.js +++ b/src/base.js @@ -1,34 +1,40 @@ /* Based on Alex Arnell's inheritance implementation. */ var Class = { - create: function(parent, properties) { - if (arguments.length == 1 && !Object.isFunction(parent)) - properties = parent, parent = null; + create: function() { + var parent = null, properties = $A(arguments); + if (Object.isFunction(properties[0])) + parent = properties.shift(); function klass() { this.initialize.apply(this, arguments); } - + + Object.extend(klass, Class.Methods); klass.superclass = parent; klass.subclasses = []; - + if (parent) { var subclass = function() { }; subclass.prototype = parent.prototype; klass.prototype = new subclass; parent.subclasses.push(klass); } - - if (properties) Class.extend(klass, properties); + + for (var i = 0; i < properties.length; i++) + klass.addMethods(properties[i]); + if (!klass.prototype.initialize) klass.prototype.initialize = Prototype.emptyFunction; - + klass.prototype.constructor = klass; - + return klass; - }, - - extend: function(destination, source) { - var ancestor = destination.superclass && destination.superclass.prototype; + } +}; + +Class.Methods = { + addMethods: function(source) { + var ancestor = this.superclass && this.superclass.prototype; for (var property in source) { var value = source[property]; @@ -41,14 +47,10 @@ var Class = { toString: function() { return method.toString() } }); } - destination.prototype[property] = value; + this.prototype[property] = value; } - return destination; - }, - - mixin: function(destination, source) { - return Object.extend(destination.prototype, source); + return this; } }; diff --git a/test/unit/base.html b/test/unit/base.html index 53e68a2..bd3feab 100644 --- a/test/unit/base.html +++ b/test/unit/base.html @@ -73,7 +73,66 @@ // empty subclass var Mouse = Class.create(Animal, {}); + + //mixins + var Sellable = { + getValue: function(pricePerKilo) { + return this.weight * pricePerKilo; + }, + + inspect: function() { + return '#'.interpolate(this); + } + }; + var Reproduceable = { + reproduce: function(partner) { + if (partner.constructor != this.constructor || partner.sex == this.sex) + return null; + var weight = this.weight / 10, sex = Math.random(1).round() ? 'male' : 'female'; + return new this.constructor('baby', weight, sex); + } + }; + + // base class with mixin + var Plant = Class.create(Sellable, { + initialize: function(name, weight) { + this.name = name; + this.weight = weight; + }, + + inspect: function() { + return '#'.interpolate(this); + } + }); + + // subclass with mixin + var Dog = Class.create(Animal, Reproduceable, { + initialize: function($super, name, weight, sex) { + this.weight = weight; + this.sex = sex; + $super(name); + } + }); + + // subclass with mixins + var Ox = Class.create(Animal, Sellable, Reproduceable, { + initialize: function($super, name, weight, sex) { + this.weight = weight; + this.sex = sex; + $super(name); + }, + + eat: function(food) { + if (food instanceof Plant) + this.weight += food.weight; + }, + + inspect: function() { + return '#'.interpolate(this); + } + }); + new Test.Unit.Runner({ testFunctionArgumentNames: function() { with(this) { @@ -419,7 +478,7 @@ testClassCreate: function() { with(this) { assert(Object.isFunction(Animal), 'Animal is not a constructor'); - assertEnumEqual([Cat, Mouse], Animal.subclasses); + assertEnumEqual([Cat, Mouse, Dog, Ox], Animal.subclasses); Animal.subclasses.each(function(subclass) { assertEqual(Animal, subclass.superclass); }); @@ -472,17 +531,17 @@ assertEqual("Gonzo: hello honk honk", gonzo.say("hello")); }}, - testClassExtend: function() { with(this) { + testClassAddMethods: function() { with(this) { var tom = new Cat('Tom'); var jerry = new Mouse('Jerry'); - Class.extend(Animal, { + Animal.addMethods({ sleep: function() { return this.say('ZZZ'); } }); - Class.extend(Mouse, { + Mouse.addMethods({ sleep: function($super) { return $super() + " ... no, can't sleep! Gotta steal cheese!"; }, @@ -498,14 +557,32 @@ assertUndefined(tom.escape); assertUndefined(new Animal().escape); - Class.extend(Animal, { + Animal.addMethods({ sleep: function() { return this.say('zZzZ'); } }); assertEqual("Jerry: zZzZ ... no, can't sleep! Gotta steal cheese!", jerry.sleep()); - }} + }}, + + testBaseClassWithMixin: function() { with(this) { + var grass = new Plant('grass', 3); + assertRespondsTo('getValue', grass); + assertEqual('#', grass.inspect()); + }}, + + testSubclassWithMixin: function() { with(this) { + var snoopy = new Dog('Snoopy', 12, 'male'); + assertRespondsTo('reproduce', snoopy); + }}, + + testSubclassWithMixins: function() { with(this) { + var cow = new Ox('cow', 400, 'female'); + assertEqual('#', cow.inspect()); + assertRespondsTo('reproduce', cow); + assertRespondsTo('getValue', cow); + }} }, 'testlog');