1 var base = require("./base/object"); 2 3 /** 4 * Used to keep track of classes and to create unique ids 5 * @ignore 6 */ 7 var classCounter = 0; 8 9 /** 10 * Alias to call the super method of a particular class 11 * 12 * @ignore 13 */ 14 var callSuper = function(args, a) { 15 if (!args && !a) { 16 throw new Error("arguments must be defined."); 17 } 18 var f = "", u; 19 var callee, m, meta = this.__meta || {}, pos, supers, l; 20 if (!a) { 21 a = args; 22 } 23 if (args.callee) { 24 callee = args.callee; 25 !f && (f = callee._name); 26 u = callee._unique; 27 pos = meta.pos,supers = meta.supers,l = meta.supers.length; 28 if (f != "constructor") { 29 if (meta.unique === u) { 30 pos = 0; 31 } else if (callee != meta.callee) { 32 pos = 0; 33 for (; pos < l; pos++) { 34 var sup = supers[pos], sm = sup ? sup[f] : null; 35 if (sm == callee) { 36 pos++; 37 break; 38 } 39 } 40 } 41 for (; pos < l; pos++) { 42 var sup = supers[pos], sm = sup ? sup[f] : null; 43 if (sm && sm != callee) { 44 m = sm; 45 break; 46 } 47 } 48 } else { 49 if (meta.unique === u) { 50 pos = 0; 51 } else if (callee != meta.callee) { 52 pos = 0; 53 for (; pos < l; pos++) { 54 var sup = supers[pos].__meta.proto; 55 if (sup.instance && sup.instance.hasOwnProperty(f)) { 56 sm = sup.instance[f]; 57 if (sm == callee) { 58 pos++; 59 break; 60 } 61 } 62 } 63 } 64 for (; pos < l; pos++) { 65 var sup = supers[pos].__meta.proto; 66 if (sup.instance && sup.instance.hasOwnProperty(f)) { 67 sm = sup.instance[f]; 68 if (sm && sm != callee) { 69 m = sm; 70 break; 71 } 72 } 73 } 74 } 75 } else { 76 throw new Error("can't find the super"); 77 } 78 meta.callee = m; 79 meta.pos = m ? ++pos : -1; 80 if (m) { 81 return m.apply(this, a); 82 } else { 83 return null; 84 } 85 }; 86 87 /** 88 * @ignore 89 */ 90 var defineMixinProps = function(child, proto, unique) { 91 if (proto) { 92 var operations = proto.setters; 93 if (operations) { 94 for (var i in operations) { 95 if (!child.__lookupSetter__(i)) { //make sure that the setter isnt already there 96 child.__defineSetter__(i, operations[i]); 97 } 98 } 99 } 100 operations = proto.getters; 101 if (operations) { 102 for (i in operations) { 103 if (!child.__lookupGetter__(i)) { 104 //define the getter if the child does not already have it 105 child.__defineGetter__(i, operations[i]); 106 } 107 } 108 } 109 if (proto) { 110 for (j in proto) { 111 if (j != "getters" && j != "setters") { 112 if (!child.hasOwnProperty(j)) { 113 child[j] = proto[j]; 114 } 115 } 116 } 117 } 118 } 119 }; 120 121 /** 122 * @ignore 123 */ 124 var mixin = function() { 125 var args = Array.prototype.slice.call(arguments); 126 var child = this.prototype, bases = child.__meta.bases, staticBases = bases.slice(), constructor = child.constructor; 127 for (var i = 0; i < args.length; i++) { 128 var m = args[i]; 129 var proto = m.prototype.__meta.proto; 130 defineMixinProps(child, proto.instance, constructor._unique); 131 defineMixinProps(this, proto.static, constructor._unique); 132 //copy the bases for static, 133 var staticSupers = this.__meta.supers, supers = child.__meta.supers; 134 child.__meta.supers = mixinSupers(m.prototype, bases).concat(supers); 135 this.__meta.supers = mixinSupers(m, staticBases).concat(staticSupers); 136 } 137 return this; 138 }; 139 140 /** 141 * @ignore 142 */ 143 var mixinSupers = function(sup, bases) { 144 var arr = [], unique = sup.__meta.unique; 145 //check it we already have this super mixed into our prototype chain 146 //if true then we have already looped their supers! 147 if (bases.indexOf(unique) == -1) { 148 arr.push(sup); 149 //add their id to our bases 150 bases.push(unique); 151 var supers = sup.__meta.supers, l = supers.length; 152 if (supers && l) { 153 for (var i = 0; i < l; i++) { 154 arr = arr.concat(mixinSupers(supers[i], bases)); 155 } 156 } 157 } 158 return arr; 159 }; 160 161 162 /** 163 * @ignore 164 */ 165 var defineProps = function(child, proto) { 166 if (proto) { 167 var operations = proto.setters; 168 if (operations) { 169 for (var i in operations) { 170 child.__defineSetter__(i, operations[i]); 171 } 172 } 173 operations = proto.getters; 174 if (operations) { 175 for (i in operations) { 176 child.__defineGetter__(i, operations[i]); 177 } 178 } 179 for (i in proto) { 180 if (i != "getters" && i != "setters") { 181 var f = proto[i]; 182 if (typeof f == "function") { 183 f._name = i; 184 f._unique = "define" + classCounter; 185 } 186 if (i != "constructor") { 187 child[i] = f; 188 } 189 } 190 } 191 } 192 }; 193 194 var export = function(obj, name) { 195 if (obj && name) { 196 obj[name] = this; 197 } else { 198 obj.exports = obj = this; 199 } 200 return this; 201 }; 202 203 204 /** 205 * @ignore 206 */ 207 var __define = function(child, sup, proto) { 208 proto = proto || {}; 209 var childProto = child.prototype, supers = []; 210 if (sup instanceof Array) { 211 supers = sup; 212 sup = supers.shift(); 213 } 214 var bases = [], meta, staticMeta; 215 if (sup) { 216 child.__proto__ = sup; 217 childProto.__proto__ = sup.prototype; 218 meta = childProto.__meta = { 219 supers : mixinSupers(sup.prototype, bases), 220 superPos : 0, 221 superName : "", 222 unique : "define" + classCounter 223 }; 224 staticMeta = child.__meta = { 225 supers : mixinSupers(sup, []), 226 superPos : 0, 227 superName : "", 228 unique : "define" + classCounter 229 }; 230 } else { 231 meta = childProto.__meta = { 232 supers : [], 233 superPos : 0, 234 superName : "", 235 unique : "define" + classCounter 236 }; 237 staticMeta = child.__meta = { 238 supers : [], 239 superPos : 0, 240 superName : "", 241 unique : "define" + classCounter 242 }; 243 } 244 meta.proto = proto; 245 childProto.static = child; 246 defineProps(childProto, proto.instance); 247 defineProps(child, proto.static); 248 meta.bases = bases; 249 staticMeta.bases = bases; 250 if (supers.length) { 251 mixin.apply(child, supers.reverse()); 252 } 253 child.mixin = mixin; 254 child.export = export; 255 child.super = callSuper; 256 childProto.super = callSuper; 257 classCounter++; 258 return child; 259 }; 260 261 262 base.merge(exports, { 263 /**@lends comb*/ 264 265 /** 266 * Defines a new class to be used 267 * 268 * <p> 269 * Class methods 270 * <ul> 271 * <li>export(module | bject, name): exports the object to module or the object with the name</li> 272 * <li>mixin(mixin) : mixes in an object</li> 273 * </ul> 274 * </br> 275 * Instance methods 276 * <ul> 277 * <li>super(argumnents, [?newargs]): calls the super of the current method</li> 278 * </ul> 279 * 280 * </br> 281 * Instance properties 282 * <ul> 283 * <li>static: use to reference class properties and methods</li> 284 * </ul> 285 * 286 * </p> 287 * 288 * 289 * @example 290 * //Class without a super class 291 * var Mammal = comb.define(null, { 292 * instance : { 293 * 294 * constructor: function(options) { 295 * options = options || {}; 296 * this.super(arguments); 297 * this._type = options.type || "mammal"; 298 * }, 299 * 300 * speak : function() { 301 * return "A mammal of type " + this._type + " sounds like"; 302 * }, 303 * 304 * //Define your getters 305 * getters : { 306 * type : function() { 307 * return this._type; 308 * } 309 * }, 310 * 311 * //Define your setters 312 * setters : { 313 * type : function(t) { 314 * this._type = t; 315 * } 316 * } 317 * }, 318 * 319 * //Define your static methods 320 * static : { 321 * soundOff : function() { 322 * return "Im a mammal!!"; 323 * } 324 * } 325 * }); 326 * 327 * //Show singular inheritance 328 *var Wolf = comb.define(Mammal, { 329 * instance: { 330 * constructor: function(options) { 331 * options = options || {}; 332 * //You can call your super constructor, or you may not 333 * //call it to prevent the super initializing parameters 334 * this.super(arguments); 335 * this._sound = "growl"; 336 * this._color = options.color || "grey"; 337 * }, 338 * 339 * speak : function() { 340 * //override my super classes speak 341 * //Should return "A mammal of type mammal sounds like a growl" 342 * return this.super(arguments) + " a " + this._sound; 343 * }, 344 * 345 * //add new getters for sound and color 346 * getters : { 347 * 348 * color : function() { 349 * return this._color; 350 * }, 351 * 352 * sound : function() { 353 * return this._sound; 354 * } 355 * }, 356 * 357 * setters : { 358 * 359 * //NOTE color is read only except on initialization 360 * 361 * sound : function(s) { 362 * this._sound = s; 363 * } 364 * } 365 * 366 * }, 367 * 368 * static : { 369 * //override my satic soundOff 370 * soundOff : function() { 371 * //You can even call super in your statics!!! 372 * //should return "I'm a mammal!! that growls" 373 * return this.super(arguments) + " that growls"; 374 * } 375 * } 376 *}); 377 * 378 * 379 * //Typical hierarchical inheritance 380 * // Mammal->Wolf->Dog 381 * var Dog = comb.define(Wolf, { 382 * instance: { 383 * constructor: function(options) { 384 * options = options || {}; 385 * this.super(arguments); 386 * //override Wolfs initialization of sound to woof. 387 * this._sound = "woof"; 388 * 389 * }, 390 * 391 * speak : function() { 392 * //Should return "A mammal of type mammal sounds like a growl thats domesticated" 393 * return this.super(arguments) + " thats domesticated"; 394 * } 395 * }, 396 * 397 * static : { 398 * soundOff : function() { 399 * //should return "I'm a mammal!! that growls but now barks" 400 * return this.super(arguments) + " but now barks"; 401 * } 402 * } 403 *}); 404 * 405 * 406 * 407 * dog instanceof Wolf => true 408 * dog instanceof Mammal => true 409 * dog.speak() => "A mammal of type mammal sounds like a woof thats domesticated" 410 * dog.type => "mammal" 411 * dog.color => "gold" 412 * dog.sound => "woof" 413 * Dog.soundOff() => "Im a mammal!! that growls but now barks" 414 * 415 * // Mammal->Wolf->Dog->Breed 416 *var Breed = comb.define(Dog, { 417 * instance: { 418 * 419 * //initialize outside of constructor 420 * _pitch : "high", 421 * 422 * constructor: function(options) { 423 * options = options || {}; 424 * this.super(arguments); 425 * this.breed = options.breed || "lab"; 426 * }, 427 * 428 * speak : function() { 429 * //Should return "A mammal of type mammal sounds like a 430 * //growl thats domesticated with a high pitch!" 431 * return this.super(arguments) + " with a " + this._pitch + " pitch!"; 432 * }, 433 * 434 * getters : { 435 * pitch : function() { 436 * return this._pitch; 437 * } 438 * } 439 * }, 440 * 441 * static : { 442 * soundOff : function() { 443 * //should return "I'M A MAMMAL!! THAT GROWLS BUT NOW BARKS!" 444 * return this.super(arguments).toUpperCase() + "!"; 445 * } 446 * } 447 * }); 448 * 449 * 450 * var breed = new Breed({color : "gold", type : "lab"}), 451 * 452 * 453 * 454 * breed instanceof Dog => true 455 * breed instanceof Wolf => true 456 * breed instanceof Mammal => true 457 * breed.speak() => "A mammal of type lab sounds like a woof " 458 * + "thats domesticated with a high pitch!" 459 * breed.type => "lab" 460 * breed.color => "gold" 461 * breed.sound => "woof" 462 * breed.soundOff() => "IM A MAMMAL!! THAT GROWLS BUT NOW BARKS!" 463 * 464 * 465 * //Example of multiple inheritance 466 * //NOTE proto is optional 467 * 468 * //Mammal is super class 469 * //Wolf Dog and Breed inject functionality into the prototype 470 * var Lab = comb.define([Mammal, Wolf, Dog, Breed]); 471 * 472 * var lab = new Lab(); 473 * lab instanceof Wolf => false 474 * lab instanceof Dog => false 475 * lab instanceof Breed => false 476 * lab instanceof Mammal => true 477 * lab.speak() => "A mammal of type mammal sounds like a" 478 * + " woof thats domesticated with a high pitch!" 479 * Lab.soundOff() => "IM A MAMMAL!! THAT GROWLS BUT NOW BARKS!" 480 * 481 * @param {Array|Class} super the supers of this class 482 * @param {Object} [proto] the object used to define this class 483 * @param {Object} [proto.instance] the instance methods of the class 484 * @param {Object} [proto.instance.getters] the getters for the class 485 * @param {Object} [proto.instance.setters] the setters for the class 486 * @param {Object} [proto.static] the Class level methods of this class 487 * @param {Object} [proto.static.getters] static getters for the object 488 * @param {Object} [proto.static.setters] static setters for the object 489 * 490 * @returns {Object} the constructor of the class to be used with new keyword 491 */ 492 define : function(super, proto) { 493 var child = function() { 494 //if a unique wasn't defined then the 495 //child didn't define one! 496 var instance = this.__meta.proto.instance; 497 if (instance && instance.constructor && instance.constructor._unique) { 498 instance.constructor.apply(this, arguments); 499 } else { 500 var supers = this.__meta.supers, l = supers.length; 501 for (var i = 0; i < l; i++) { 502 var protoInstance = supers[i].__meta.proto.instance; 503 if (protoInstance && protoInstance.hasOwnProperty("constructor")) { 504 protoInstance.constructor.apply(this, arguments); 505 break; 506 } 507 } 508 //this.super("constructor", arguments); 509 } 510 511 }; 512 return __define(child, super, proto); 513 }, 514 515 /** 516 * Defines a singleton instance of a Class 517 * @example 518 * var MyLab = comb.singleton([Mammal, Wolf, Dog, Breed]); 519 * var myLab1 = new MyLab(); 520 * myLab1.type = "collie" 521 * var myLab2 = new MyLab(); 522 * myLab1 === myLab2 => true 523 * myLab1.type => "collie" 524 * myLab2.type => "collie" 525 * See {@link define} 526 */ 527 singleton : function(super, proto) { 528 var retInstance; 529 var child = function() { 530 if (!retInstance) { 531 //if a unique wasn't defined then the 532 //child didn't define one! 533 var instance = this.__meta.proto.instance; 534 if (instance && instance.constructor._unique) { 535 instance.constructor.apply(this, arguments); 536 } else { 537 var supers = this.__meta.supers, l = supers.length; 538 for (var i = 0; i < l; i++) { 539 var protoInstance = supers[i].__meta.proto.instance; 540 if (protoInstance && protoInstance.hasOwnProperty("constructor")) { 541 protoInstance.constructor.apply(this, arguments); 542 break; 543 } 544 } 545 } 546 retInstance = this; 547 } 548 return retInstance; 549 }; 550 return __define(child, super, proto); 551 } 552 }); 553