-
Notifications
You must be signed in to change notification settings - Fork 0
/
mojo.js
156 lines (140 loc) · 4.55 KB
/
mojo.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
var mojoClass = (function() {
var mc = {
/**
* Returns a comma-separated list of the types of each item in an array.
* If an item is a mojoClass, it will return 'mojo' instead of 'function'.
*/
types: function(array) {
var types = [], type;
var i;
for(i = 0; i < array.length; i++) {
type = array[i]._hasMojo ? 'mojo' : typeof(array[i]);
types.push(type);
}
return types.join(',');
},
/**
* Wrapper for bind so we can support browsers without native support.
*/
bind: function(that, func) {
if(typeof(func) !== 'function') {
// Only functions can be bound, but for readability of calling code, we
// don't want to worry about calling bind on non-functions.
return func;
}
if(func.bind) {
// Use built-in implementation of bind
return func.bind(that);
}
return function() {
return func.apply(that, arguments);
}
}
};
var mojoClass = function (/* [baseClass,] constructor, attributes */) {
var baseClass = null, constructor, attributes;
switch(mc.types(arguments)) {
case 'function,object':
attributes = arguments[1];
constructor = arguments[0];
break;
case 'mojo,function,object':
attributes = arguments[2];
baseClass = arguments[0];
constructor = arguments[1];
break;
case 'mojo,object':
attributes = arguments[1];
baseClass = arguments[0];
constructor = baseClass._constructor;
break;
case 'object':
attributes = arguments[0];
constructor = function() { };
break;
default:
throw 'Invalid mojo class declaration: [' + mc.types(arguments) + ']';
}
var extend = function(baseObject, additions) {
var key;
for(key in additions) {
if(additions.hasOwnProperty(key)) {
baseObject[key] = additions[key];
}
}
}
var mojoClassFactory = function() {
var key;
// Build the list of attributes this class will have. It includes everything in
// the base class, plus the attributes in the class declaration.
var classAttributes = {};
if(baseClass) {
extend(classAttributes, baseClass._attributes);
}
extend(classAttributes, attributes);
// Copy those attributes to this new object, binding functions as we go
for(key in classAttributes) {
if(!classAttributes.hasOwnProperty(key)) {
break;
}
this[key] = mc.bind(this, classAttributes[key]);
}
// Our _super property gives us access to the properties and methods of
// the parent class; copy them over, binding the functions to this
this._super = {};
this._superClass = baseClass;
if(baseClass) {
for(key in baseClass._attributes) {
if(!baseClass._attributes.hasOwnProperty(key)) {
break;
}
this._super[key] = mc.bind(this, baseClass._attributes[key]);
}
this._super._constructor = baseClass._constructor;
}
// Run the constructor
if(!mc.makingPrototype) {
this._constructor = constructor;
this._constructor.apply(this, arguments);
}
return this;
};
mojoClassFactory._constructor = constructor;
mojoClassFactory._attributes = attributes;
mojoClassFactory._hasMojo = true;
// If we're just making an instance for the .prototype property, don't
// actually run the whole constructor.
// This clever idea from http://ejohn.org/blog/simple-javascript-inheritance/
if(mc.makingPrototype) {
return false;
}
mc.makingPrototype = true;
mojoClassFactory.prototype = baseClass ? new baseClass() : Object;
mc.makingPrototype = false;
return mojoClassFactory;
};
/**
* This allows you to define abstract base classes that cannot be
* instantiated directly.
* e.g. var someClass = mojoClass.abstract(constructor, attributes);
*/
mojoClass.abstract = function() {
function wrapConstructor(constructor) {
return function() {
if(!this._super._constructor) {
throw "Cannot instantiate this class directly; subclass it first";
}
constructor.apply(this, arguments);
}
}
var i;
for(i = 0; i < arguments.length; i++) {
if(typeof arguments[i] === 'function') {
arguments[i] = wrapConstructor(arguments[i]);
break;
}
}
return mojoClass.apply(this, arguments);
};
return mojoClass;
}());