Skip to content

Commit 9526401

Browse files
author
Christopher Blum
committed
This should be it.
1 parent 8b517f9 commit 9526401

File tree

6 files changed

+383
-0
lines changed

6 files changed

+383
-0
lines changed

README

Whitespace-only changes.

README.textile

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
h1. Class.create([superclass][, methods...])
2+
3+
* _superclass_ (Class) – The optional superclass to inherit methods from.
4+
* _methods_ (Object) – An object whose properties will be "mixed-in" to the new class. Any number of mixins can be added; later mixins take precedence.
5+
6+
*Class.create* creates a class and returns a constructor function for instances of the class. Calling the constructor function (typically as part of a new statement) will invoke the class's initialize method.
7+
8+
*Class.create* accepts two kinds of arguments. If the first argument is a Class, it's used as the new class's superclass, and all its methods are inherited. Otherwise, any arguments passed are treated as objects, and their methods are copied over ("mixed in") as instance methods of the new class. In cases of method name overlap, later arguments take precedence over earlier arguments.
9+
10+
If a subclass overrides an instance method declared in a superclass, the subclass's method can still access the original method. To do so, declare the subclass's method as normal, but insert $super as the first argument. This makes $super available as a method for use within the function.
11+
12+
*For details, see the ["inheritance tutorial on the Prototype website":http://prototypejs.org/learn/class-inheritance].*

class.js

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
Based on Alex Arnell's inheritance implementation.
3+
Ported by Christopher Blum (http://github.com/tiff) from Prototype (https://github.com/sstephenson/prototype) to be compatible with jQuery
4+
*/
5+
6+
var Class = (function($) {
7+
8+
// Some versions of JScript fail to enumerate over properties, names of which
9+
// correspond to non-enumerable properties in the prototype chain
10+
var IS_DONTENUM_BUGGY = (function(){
11+
for (var p in { toString: 1 }) {
12+
// check actual property name, so that it works with augmented Object.prototype
13+
if (p === 'toString') return false;
14+
}
15+
return true;
16+
})();
17+
18+
// Get argument names from method as array
19+
function getArgumentNames(method) {
20+
var names = method.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1]
21+
.replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '')
22+
.replace(/\s+/g, '').split(',');
23+
return names.length == 1 && !names[0] ? [] : names;
24+
}
25+
26+
// Wrap a given method
27+
function wrap(method, wrapper) {
28+
return function() {
29+
var args = $.makeArray(arguments);
30+
args.unshift($.proxy(method, this));
31+
return wrapper.apply(this, args);
32+
}
33+
}
34+
35+
// Grab all keys from an object
36+
// keys({ a: 1, b: 2 }); // => ["a", "b"];
37+
function keys(object) {
38+
if (Object.keys) {
39+
return Object.keys(object);
40+
} else {
41+
var results = [];
42+
for (var property in object) {
43+
if (object.hasOwnProperty(property)) {
44+
results.push(property);
45+
}
46+
}
47+
return results;
48+
}
49+
}
50+
51+
function subclass() {};
52+
function create() {
53+
var parent = null, properties = $.makeArray(arguments);
54+
if ($.isFunction(properties[0]))
55+
parent = properties.shift();
56+
57+
function klass() {
58+
this.initialize.apply(this, arguments);
59+
}
60+
61+
$.extend(klass, Class.Methods);
62+
klass.superclass = parent;
63+
klass.subclasses = [];
64+
65+
if (parent) {
66+
subclass.prototype = parent.prototype;
67+
klass.prototype = new subclass;
68+
parent.subclasses.push(klass);
69+
}
70+
71+
for (var i = 0, length = properties.length; i < length; i++)
72+
klass.addMethods(properties[i]);
73+
74+
if (!klass.prototype.initialize)
75+
klass.prototype.initialize = $.noop;
76+
77+
klass.prototype.constructor = klass;
78+
return klass;
79+
}
80+
81+
function addMethods(source) {
82+
var ancestor = this.superclass && this.superclass.prototype,
83+
properties = keys(source);
84+
85+
// IE6 doesn't enumerate `toString` and `valueOf` (among other built-in `Object.prototype`) properties,
86+
// Force copy if they're not Object.prototype ones.
87+
// Do not copy other Object.prototype.* for performance reasons
88+
if (IS_DONTENUM_BUGGY) {
89+
if (source.toString != Object.prototype.toString)
90+
properties.push("toString");
91+
if (source.valueOf != Object.prototype.valueOf)
92+
properties.push("valueOf");
93+
}
94+
95+
for (var i = 0, length = properties.length; i < length; i++) {
96+
var property = properties[i], value = source[property];
97+
if (ancestor && $.isFunction(value) &&
98+
getArgumentNames(value)[0] == "$super") {
99+
var method = value;
100+
value = wrap(function(m) {
101+
return function() { return ancestor[m].apply(this, arguments); };
102+
}(property), method);
103+
104+
value.valueOf = $.proxy(method.valueOf, method);
105+
value.toString = $.proxy(method.toString, method);
106+
}
107+
this.prototype[property] = value;
108+
}
109+
110+
return this;
111+
}
112+
113+
return {
114+
create: create,
115+
keys: keys,
116+
Methods: {
117+
addMethods: addMethods
118+
}
119+
};
120+
})(jQuery);

test/fixtures.js

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// base class
2+
var Animal = Class.create({
3+
initialize: function(name) {
4+
this.name = name;
5+
},
6+
name: "",
7+
eat: function() {
8+
return this.say("Yum!");
9+
},
10+
say: function(message) {
11+
return this.name + ": " + message;
12+
}
13+
});
14+
15+
// subclass that augments a method
16+
var Cat = Class.create(Animal, {
17+
eat: function($super, food) {
18+
if (food instanceof Mouse) return $super();
19+
else return this.say("Yuk! I only eat mice.");
20+
}
21+
});
22+
23+
// empty subclass
24+
var Mouse = Class.create(Animal, {});
25+
26+
//mixins
27+
var Sellable = {
28+
getValue: function(pricePerKilo) {
29+
return this.weight * pricePerKilo;
30+
},
31+
32+
inspect: function() {
33+
return '#<Sellable: #{weight}kg>'.replace("#{weight}", this.weight);
34+
}
35+
};
36+
37+
var Reproduceable = {
38+
reproduce: function(partner) {
39+
if (partner.constructor != this.constructor || partner.sex == this.sex)
40+
return null;
41+
var weight = this.weight / 10, sex = Math.round(Math.random(1)) ? 'male' : 'female';
42+
return new this.constructor('baby', weight, sex);
43+
}
44+
};
45+
46+
// base class with mixin
47+
var Plant = Class.create(Sellable, {
48+
initialize: function(name, weight) {
49+
this.name = name;
50+
this.weight = weight;
51+
},
52+
53+
inspect: function() {
54+
return "#<Plant: #{name}>".replace("#{name}", this.name);
55+
}
56+
});
57+
58+
// subclass with mixin
59+
var Dog = Class.create(Animal, Reproduceable, {
60+
initialize: function($super, name, weight, sex) {
61+
this.weight = weight;
62+
this.sex = sex;
63+
$super(name);
64+
}
65+
});
66+
67+
// subclass with mixins
68+
var Ox = Class.create(Animal, Sellable, Reproduceable, {
69+
initialize: function($super, name, weight, sex) {
70+
this.weight = weight;
71+
this.sex = sex;
72+
$super(name);
73+
},
74+
75+
eat: function(food) {
76+
if (food instanceof Plant)
77+
this.weight += food.weight;
78+
},
79+
80+
inspect: function() {
81+
return "#<Ox: #{name}>".replace("#{name}", this.name);
82+
}
83+
});

test/index.html

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>jQuery - Class.create</title>
5+
<link rel="stylesheet" href="http://code.jquery.com/qunit/git/qunit.css" media="screen">
6+
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js"></script>
7+
<script src="http://code.jquery.com/qunit/git/qunit.js"></script>
8+
<script src="../class.js"></script>
9+
<script src="fixtures.js"></script>
10+
<script src="test.js"></script>
11+
</head>
12+
<body>
13+
<h1 id="qunit-header">jQuery - Class.create</h1>
14+
<h2 id="qunit-banner"></h2>
15+
<div id="qunit-testrunner-toolbar"></div>
16+
<h2 id="qunit-userAgent"></h2>
17+
<ol id="qunit-tests"></ol>
18+
<div id="qunit-fixture"></div>
19+
</body>
20+
</html>

test/test.js

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
module("Class.create");
2+
3+
(function($) {
4+
5+
test("Creation", function() {
6+
ok($.isFunction(Animal), "Animal is not a constructor");
7+
same(Animal.subclasses, [Cat, Mouse, Dog, Ox]);
8+
$.each(Animal.subclasses, function(i, subclass) {
9+
equals(subclass.superclass, Animal);
10+
});
11+
12+
var Bird = Class.create(Animal);
13+
equals(Animal.subclasses[Animal.subclasses.length - 1], Bird);
14+
// for..in loop (for some reason) doesn't iterate over the constructor property in top-level classes
15+
same(Class.keys(new Animal).sort(), Class.keys(new Bird).sort());
16+
});
17+
18+
19+
test("Instantiation", function() {
20+
var pet = new Animal("Nibbles");
21+
equals(pet.name, "Nibbles", "property not initialized");
22+
equals(pet.say("Hi!"), "Nibbles: Hi!");
23+
equals(Animal, pet.constructor, "bad constructor reference");
24+
ok(!pet.superclass);
25+
26+
var Empty = Class.create();
27+
equals(typeof(new Empty), "object");
28+
});
29+
30+
31+
test("Inheritance", function() {
32+
var tom = new Cat("Tom");
33+
equals(tom.constructor, Cat, "bad constructor reference");
34+
equals(tom.constructor.superclass, Animal, "bad superclass reference");
35+
equals(tom.name, "Tom");
36+
equals(tom.say('meow'), "Tom: meow");
37+
equals(tom.eat(new Animal), "Tom: Yuk! I only eat mice.");
38+
});
39+
40+
41+
test("Superclass Method Call", function() {
42+
var tom = new Cat("Tom");
43+
equals(tom.eat(new Mouse), "Tom: Yum!");
44+
45+
// augment the constructor and test
46+
var Dodo = Class.create(Animal, {
47+
initialize: function($super, name) {
48+
$super(name);
49+
this.extinct = true;
50+
},
51+
52+
say: function($super, message) {
53+
return $super(message) + " honk honk";
54+
}
55+
});
56+
57+
var gonzo = new Dodo("Gonzo");
58+
equals(gonzo.name, "Gonzo");
59+
ok(gonzo.extinct, "Dodo birds should be extinct");
60+
equals(gonzo.say("hello"), "Gonzo: hello honk honk");
61+
});
62+
63+
64+
test("Add Methods", function() {
65+
var tom = new Cat("Tom");
66+
var jerry = new Mouse("Jerry");
67+
68+
Animal.addMethods({
69+
sleep: function() {
70+
return this.say("ZZZ");
71+
}
72+
});
73+
74+
Mouse.addMethods({
75+
sleep: function($super) {
76+
return $super() + " ... no, can't sleep! Gotta steal cheese!";
77+
},
78+
escape: function(cat) {
79+
return this.say("(from a mousehole) Take that, " + cat.name + "!");
80+
}
81+
});
82+
83+
equals(tom.sleep(), "Tom: ZZZ", "added instance method not available to subclass");
84+
equals(jerry.sleep(), "Jerry: ZZZ ... no, can't sleep! Gotta steal cheese!");
85+
equals(jerry.escape(tom), "Jerry: (from a mousehole) Take that, Tom!");
86+
// insure that a method has not propagated *up* the prototype chain:
87+
ok(!tom.escape);
88+
ok(!new Animal().escape);
89+
90+
Animal.addMethods({
91+
sleep: function() {
92+
return this.say('zZzZ');
93+
}
94+
});
95+
96+
equals(jerry.sleep(), "Jerry: zZzZ ... no, can't sleep! Gotta steal cheese!");
97+
});
98+
99+
100+
test("Base Class With Mixin", function() {
101+
var grass = new Plant("grass", 3);
102+
ok($.isFunction(grass.getValue));
103+
equals(grass.inspect(), "#<Plant: grass>");
104+
});
105+
106+
107+
test("Subclass With Mixin", function() {
108+
var snoopy = new Dog("Snoopy", 12, "male");
109+
ok($.isFunction(snoopy.reproduce));
110+
});
111+
112+
113+
test("Subclass With Mixins", function() {
114+
var cow = new Ox("cow", 400, "female");
115+
equals(cow.inspect(), "#<Ox: cow>");
116+
ok($.isFunction(cow.reproduce));
117+
ok($.isFunction(cow.getValue));
118+
});
119+
120+
121+
test("Class With toString And valueOf Methods", function() {
122+
var Foo = Class.create({
123+
toString: function() { return "toString" },
124+
valueOf: function() { return "valueOf" }
125+
});
126+
127+
var Bar = Class.create(Foo, {
128+
valueOf: function() { return "myValueOf" }
129+
});
130+
131+
var Parent = Class.create({
132+
m1: function(){ return 'm1' },
133+
m2: function(){ return 'm2' }
134+
});
135+
var Child = Class.create(Parent, {
136+
m1: function($super) { return 'm1 child' },
137+
m2: function($super) { return 'm2 child' }
138+
});
139+
140+
ok(new Child().m1.toString().indexOf("m1 child") > -1);
141+
142+
equals(new Foo().toString(), "toString");
143+
equals(new Foo().valueOf(), "valueOf");
144+
equals(new Bar().toString(), "toString");
145+
equals(new Bar().valueOf(), "myValueOf");
146+
});
147+
148+
})(jQuery);

0 commit comments

Comments
 (0)