-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
157 lines (126 loc) · 4.93 KB
/
index.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
157
/***** GENERIC METHODS FOR INHERITENCE ******/
//gets a main and sub classes, and sets the prototype correctly, with extra useful methods
function extend(SubClass, MainClass) {
SubClass.prototype.__proto__ = MainClass.prototype;
SubClass.extendClass = extendClass;
SubClass.defineMethod = defineMethod;
SubClass.defineProperty = defineProperty;
return SubClass;
}
//gets a sub class, and sets the calling context class as the main one for the sub prototype
function extendClass(SubClass) {
if (!SubClass) {
return subClass.call(this);
}
return extend(SubClass, this);
}
//creates a new class and sets the current calling context class as the main class for it.
function subClass() {
var SubClass = function () {
this.parentConstructor.apply(this, arguments);
};
return extendClass.call(this, SubClass);
}
//allows to define named methods directly in the prototype of the class
function defineMethod(methodCallback, methodName) {
if (typeof methodCallback !== 'function') {
throw 'Method should be a function';
}
if (!methodName && !methodCallback.name) {
throw 'You should provide a name to the method either in definition or as a second parameter name';
}
methodName = methodCallback.name || methodName;
this.prototype[methodName] = methodCallback;
return this;
};
//allows to define a property with setter and getter
function defineProperty(property, getter, setter, descriptors){
descriptors = descriptors || {};
descriptors.get = descriptors.get || getter || undefined;
descriptors.set = descriptors.set || setter || undefined;
Object.defineProperty(this.prototype, property, descriptors);
return this;
}
/**** THE MAIN OOP CLASS ****/
var OOP;
(function(){
OOP = function OOP() {
};
// Allows to call parent constructor from sub constructors. i.e. this.parentConstructor()
OOP.prototype.parentConstructor = function () {
//get arguments list
var args = Array.prototype.slice.call(arguments);
args.unshift('constructor');
return this.parentMethod.apply(this, args);
};
// Allows to call a parent method from sub classes after they are overridden. i.e. this.parentMethod(methodName, param1, param2, ...);
OOP.prototype.parentMethod = function (method) {
if (!method) {
throw 'What method to call in parent?';
}
//get arguments list
var args = Array.prototype.slice.call(arguments);
args.shift();
//checks if a method is an instuctor in current object stack
function isConstructor(method){
var parent = this;
do {
if(parent.constructor===method){
return true;
}
parent = parent.__proto__;
} while(parent);
return false;
}
//find overriding calling method
var callingMethod = arguments.callee.caller;
while(callingMethod){
if(callingMethod.name===method || method==='constructor' && isConstructor.call(this, callingMethod)){
break;
}
callingMethod = callingMethod.caller;
}
if(!callingMethod) {
throw 'Calling parent method from outside method context';
}
//find called method
var found = false, parent = this, calledMethod;
if(typeof callingMethod !== 'function'){
throw 'Calling parent method from a normal context!';
}
while(parent){
if(parent.hasOwnProperty(method) || method==='constructor'){
if(found && (method==='constructor' && callingMethod !== parent.constructor || method!=='constructor')){
calledMethod = method==='constructor' ? parent.constructor : parent[method];
break;
} else if( parent[method] === callingMethod || method==='constructor' && callingMethod === parent.constructor) {
found = true;
}
}
parent = parent.__proto__;
}
//if no called method found
if(!calledMethod){
throw 'Method ' + method + ' not found in parent levels!';
}
//call the parent method
return calledMethod.apply(this, args);
};
// Allows to call parent method directly without the name. Named methods should be used to define methods.
// Defnition example: MainClass.prototype.talk = function talk(param1){ ... }
// Calling example: SubClass.prototype.talk = function talk(param1, param2){ this.propagateCall(param1); ... }; //this will call the parent talk method with param1 passed
OOP.prototype.propagateCall = function () {
var method = arguments.callee.caller.name;
if(!method){
throw 'Can not propagate method call for unnamed methods. Use named functions to use this method.';
}
var args = Array.prototype.slice.call(arguments || []);
args.unshift(method);
return this.parentMethod.apply(this, args);
};
// Add inheritence methods
OOP.extendClass = extendClass;
OOP.extend = extend;
})();
//end
module.exports = OOP;