1 /** @ignore */ 2 (function(){ 3 //////////////////////////////////////////////////////////////////////////// 4 // 5 // Classes 6 // 7 //////////////////////////////////////////////////////////////////////////// 8 9 // Class definition borrows heavily from John Resig's blog post at: 10 // http://ejohn.org/blog/simple-javascript-inheritance/ 11 // This in turn borrows heavily from Dean Edward's Base 2: 12 // http://code.google.com/p/base2/ 13 // Class definition is secondary to the core traits functionality, which 14 // is the focus of this project. Mssr. Resig's class implementation was 15 // chosen to support traits because it's concise and adds a 'super' reference 16 // to 'this', which allows trait methods to more effectively preserve the 17 // "flattening property" as described in the traits research paper. 18 // 19 // Just about any JavaScript implementation of pseudo-classical inheritance 20 // could be plugged in here (YUI's, Tibco GI's, Ext's, etc.) with little 21 // difficulty. 22 23 24 // initializing is a flag used in the constructor to indicate that we want 25 // to create a dummy instance of a class for the purpose of prototype 26 // chaining. We avoid a normal constructor call because executing the full 27 // constructor may have expensive and undesired side effects. 28 var initializing = false; 29 30 // fnTest is applied to methods to check if they have references to _super. 31 // The machinery used to hook up _super is slightly expensive, so we want 32 // to avoid enabling it when possible. 33 // Unfortunately, not all JS implementations will return the source code 34 // of a function when casted to a String, so we do a capability test here. 35 // JS implementations that can not inspect source code will always set up 36 // _super in method calls while the better implementations will only set up 37 // _super if the word _super is found in the source code. Note that this 38 // means _super won't be set up properly if you do something silly like 39 // this["_sup"+"er"]() 40 var fnTest = /xyz/.test(function(){xyz;}) ? 41 /\b_super\b/ : 42 { test: function(){return true;} }; 43 44 45 // Traits may be added to instances via the public instance method 'use'. 46 // This function accomplishes that functionality (and is aliased to 'use' 47 // below. 48 var instanceUse = function(uses) { 49 // create a new subclass of this object's class with the new trait info 50 var anonymousSubClass = Class.define({ 51 superclass: this.prototype, 52 uses: uses 53 }); 54 55 // make the object an instance of the new subclass 56 this.prototype = anonymousSubClass.prototype; 57 }; 58 59 var klassUse = function(uses, _klass_prototype, _klass_properties) { 60 _klass_prototype = _klass_prototype || this.prototype; 61 _klass_properties = _klass_properties || this; 62 63 var trait = Trait.define({ 64 uses: o.uses, 65 _klass_prototype: _klass_prototype, 66 _klass_properties: _klass_properties 67 }); 68 69 // Augment the class with the newly created trait 70 for (var method_name in trait._exports) { 71 if (!trait._exports.hasOwnProperty(method_name)) continue; 72 73 // Class methods (but not superclass methods) have precedence over 74 // trait methods, so don't import the method if this class already 75 // has a method with the same name. 76 if (_klass_properties.hasOwnProperty(method_name)) { 77 // If the trait method is being overriden by the class, the 78 // corresponding class slot must be callable. 79 if (typeof _klass_properties[method_name] != "function") { 80 throw new Trait.TraitError(method_name + 81 " overrides trait method and must be a function."); 82 } 83 } else { 84 prop[method_name] = trait._exports[method_name]; 85 } 86 } 87 }; 88 89 /** 90 * Global namespace for class related functions. 91 * @namespace 92 * @name Class 93 */ 94 this.Class = function(){}; 95 96 /** 97 * Define a new class. In the traits model of object oriented programming 98 * a class consists of: 99 * <ul> 100 * <li>a superclass</li> 101 * <li>a set of composed traits</li> 102 * <li>a collection of methods and state variables</li> 103 * </ul> 104 * This structure is directly mirrored in the argument structure of 105 * Class.define. 106 * 107 * <p> 108 * A number of special properties are assigned to the class at definition 109 * time: 110 * </p> 111 * <ul> 112 * <li>A static reference 'superclass' is added which points at the 113 * superclass's constructor function. 114 * </li> 115 * <li>The static 'constructor' reference is pointed at the class itself so 116 * that the 'typeof' and 'instanceof' operators will behave as expected. 117 * </li> 118 * <li>A static and instance method 'does' is added as well. This method 119 * behaves just like {@link Trait#does} with the notable difference that it 120 * also includes any superclasses's traits in its output. 121 * </li> 122 * <li>Finally, an instance method '_super' is added such that invoking it in 123 * any other instance method will call the first function with the same name 124 * defined in the class's superclass prototype chain. 125 * </li> 126 * </ul> 127 * 128 * <p> 129 * Conflicts among instance properties are resolved in the following order: 130 * class members override trait members and superclass members, and trait 131 * members override superclass members. 132 * </p> 133 * 134 * <p> 135 * The class constructor is specified as a method named init passed into the 136 * o.members argument. 137 * </p> 138 * 139 * @example 140 * var HappyFace = Class.define({ 141 * superclass: Doodle, 142 * uses: [ 143 * TFace, 144 * TColoredCircle.aliases({drawColoredCircle: 'draw'}) 145 * ], 146 * members: { 147 * // constructor 148 * init: function(color) { 149 * this.isDrawn = false; 150 * if (color) 151 * this.setColor(color); 152 * }, 153 * // draw a happy face 154 * draw: function() { 155 * // call Doodle's draw method to set up the canvas 156 * this._super(); 157 * // draw a colored circle 158 * this.drawColoredCircle(); 159 * // draw the face 160 * this.drawEyes(); 161 * this.drawMouth(): 162 * // record that the happy face has been drawn 163 * this.isDrawn = true; 164 * }, 165 * // color of the happy face (default is yellow) 166 * color: 'yellow', 167 * getColor: function() { return this.color }, 168 * setColor: function(color) { this.color = color } 169 * } 170 * }); 171 * 172 * // draw a blue happy face 173 * var hf = new HappyFace('blue'); 174 * hf.draw(); 175 * log(hf.isDrawn); // => true 176 * log(hf.does(TFace)); // => true 177 * log(HappyFace.does(TColoredCircle)); // => true 178 * log(HappyFace.superclass === Doodle); // => true 179 * 180 * @name Class.define 181 * @static 182 * @function 183 * 184 * @throws {Trait.TraitError} Throws an error if the trait arguments are 185 * invalid, there is an unresolved conflict, or there is an unfullfilled 186 * trait requirement. 187 * @return {Function} Constructor for the newly created class. 188 * @param {Object} o The class configuration object. 189 * @param {Function} [o.superclass] Superclass from which this class 190 * inherits. Default superclass is the global object Class. 191 * @param {Trait|Trait[]} [o.uses] A list of traits that will be composed 192 * into this class. This happens by first constructing a new anonymous 193 * trait and then adding each of that anonymous trait's exported methods 194 * into the class prototype. Trait methods are not copied, however, if 195 * there is a method defined at the class level with the same name 196 * (because class level methods override trait level methods). Unlike 197 * normal trait definition, all trait requirements must be fullfilled at 198 * class definition time, either by one of the composed traits, a class 199 * method, or a superclass method. See the documentation for o.uses in 200 * {@link Trait} for full details on how to specify this argument. 201 * @param {Object} [o.members] Public instance members to be copied into this 202 * class's prototype. 203 */ 204 Class.define = function(o) { 205 if (!o.superclass) { 206 o.superclass = Class; 207 } 208 209 var _super = o.superclass.prototype, 210 prop = o.members || {}; 211 212 // Instantiate a base class (but only create the instance, 213 // don't run the init constructor) 214 initializing = true; 215 var prototype = new o.superclass(); 216 initializing = false; 217 218 // Merge in traits (if any) to list of properties (prop) 219 if (o.uses) { 220 var trait = Trait.define({ 221 uses: o.uses, 222 _klass_prototype: prototype, 223 _klass_properties: prop 224 }); 225 226 // Augment the class with the newly created trait 227 for (var method_name in trait._exports) { 228 if (!trait._exports.hasOwnProperty(method_name)) continue; 229 230 // Class methods (but not superclass methods) have precedence over 231 // trait methods, so don't import the method if this class already 232 // has a method with the same name. 233 if (prop.hasOwnProperty(method_name)) { 234 // If the trait method is being overriden by the class, the 235 // corresponding class slot must be callable. 236 if (typeof prop[method_name] != "function") { 237 throw new Trait.TraitError(method_name + 238 " overrides trait method and must be a function."); 239 } 240 } else { 241 prop[method_name] = trait._exports[method_name]; 242 } 243 } 244 } 245 246 // Copy the properties over to the new prototype 247 for (var name in prop) { 248 if (!prop.hasOwnProperty(name)) continue; 249 250 // Check if we're overwriting an existing function. If we are, 251 // create a closure to point this._super to the overriden method 252 // in the superclass only while the execution is running. We 253 // preserve the original value of _super and restore it so that if 254 // a method x calls another method y this._super will work as 255 // expected in method x after calling y. 256 prototype[name] = typeof prop[name] == "function" && 257 typeof _super[name] == "function" && fnTest.test(prop[name]) ? 258 (function(name, fn){ 259 return function() { 260 var tmp = this._super; 261 262 // Add a new ._super() method that is the same method 263 // but on the superclass. Naming it _super because 264 // super (no underscore) is a JS reserved word. 265 this._super = _super[name]; 266 267 // The method only need to be bound temporarily, so we 268 // remove it when we're done executing 269 var ret = fn.apply(this, arguments); 270 this._super = tmp; 271 272 return ret; 273 }; 274 })(name, prop[name]) : 275 prop[name]; 276 } 277 278 // The dummy class constructor 279 var klass = function() { 280 // All construction is actually done in the init method 281 if ( !initializing && this.init ) { 282 this.init.apply(this, arguments); 283 } 284 } 285 286 // Populate our constructed prototype object 287 klass.prototype = prototype; 288 289 // Enforce the constructor to be what we expect 290 klass.constructor = klass; 291 292 // Add a static reference to the superclass 293 klass.superclass = o.superclass; 294 295 // Alias the does operator from the implict anonymous trait as an 296 // instance and static method of the class. Return false/empty if 297 // no traits are associated with this class (or any superclass). 298 klass.does = function(trait_ref) { 299 if (!trait) { 300 if (klass.superclass.does) 301 return klass.superclass.does(trait_ref); 302 if (trait_ref) 303 return false; 304 return []; 305 } 306 307 var used_traits = trait.does(trait_ref); 308 if (klass.superclass.does) { 309 if (trait_ref) 310 return used_traits || klass.superclass.does(trait_ref); 311 // merge inherited traits and those used in this class (note: 312 // concat([]) is an idiom for copying an array). It would be quicker 313 // to sort these arrays and then merge algorithmically, but JS 314 // doesn't appear to sort references consistently using the 315 // Array.sort method...it sorts by lexicographical order of source 316 // code string in Gecko, which is definitely not what we want... 317 var inherited_traits = klass.superclass.does(trait_ref).concat([]); 318 for (var i = used_traits.length-1; i >= 0; i--) { 319 if (inherited_traits.indexOf(used_traits[i]) === -1) 320 inherited_traits.push(used_traits[i]); 321 } 322 return inherited_traits; 323 } 324 325 return used_traits; 326 }; 327 328 if (!klass.prototype.hasOwnProperty("does")) { 329 klass.prototype.does = klass.does; 330 } 331 332 if (!klass.prototype.hasOwnProperty("use")) { 333 klass.prototype.use = instanceUse; 334 } 335 336 return klass; 337 }; 338 339 //////////////////////////////////////////////////////////////////////////// 340 // 341 // Traits 342 // 343 //////////////////////////////////////////////////////////////////////////// 344 345 // Some helper functions used by Trait 346 347 // Is o an array? 348 function isArray(o) { 349 return Object.prototype.toString.apply(o) === '[object Array]'; 350 } 351 352 // Make o an array if it isn't one already 353 function makeArray(o) { 354 if (o) 355 return isArray(o) ? o : [o]; 356 return []; 357 } 358 359 // Convert ['a', 'b'] to { 'a':true, 'b':true } 360 // Also converts 'a' to { 'a':true } 361 function stringArrayToHash(a) { 362 if (!a) return {}; 363 var ret = {}; 364 if (!isArray(a)) { 365 ret[a] = true; 366 return ret; 367 } 368 for (var i = a.length-1; i >=0; i--) 369 ret[a[i]] = true; 370 return ret; 371 } 372 373 // Merge sender's properties into receiver 374 function merge(receiver, sender) { 375 for (var i in sender) { 376 if (!sender.hasOwnProperty(i)) continue; 377 receiver[i] = sender[i]; 378 } 379 return receiver; 380 } 381 382 this.Trait = Class.define({ 383 members: /** @lends Trait.prototype */ { 384 /** 385 * <p> 386 * A trait is a group of pure methods that serves as a building block for 387 * classes and is a primitive unit of code reuse. In this model, classes 388 * are composed from a set of traits by specifying glue code that 389 * connects the traits together and accesses the necessary state. If you 390 * are unfamiliar with the general object oriented programming notion of 391 * a trait it would serve you well to check out the 392 * <a href="http://code.google.com/p/jstraits/wiki/TraitSynopsis">synopsis 393 * and examples</a> before reading the rest of this documentation. 394 * </p> 395 * 396 * <p> 397 * The constructor creates a new trait for use in other traits or classes. 398 * The factory method {@link Trait.define} is the preferred way to 399 * instantiate new traits, as opposed to calling 'new Trait(...)' 400 * directly. 401 * </p> 402 * 403 * @example 404 * var TColoredCircle = new Trait({ 405 * uses: [TColor, TCircle.aliases({'drawOutline': 'draw'})], 406 * requires: 'fillWithColor', 407 * methods: { 408 * draw: function() { 409 * // draw a colored circle 410 * this.drawOutline(); 411 * this.fillWithColor(this.getColor()); 412 * } 413 * } 414 * }); 415 * 416 * @constructs 417 * @see Trait.define 418 * @throws {Trait.TraitError} Throws an error if the trait definition 419 * arguments are invalid, the trait definition is inconsistent, or 420 * there is an unresolved conflict. 421 * @param {Object} o The trait configuration object. 422 * @param {Trait|Trait[]} [o.uses] Other trait(s) that will be composed 423 * into this new trait. Note that trait composition is both 424 * associative and commutative, so if specifying more than one trait 425 * the order does not matter. To alias or exclude methods from 426 * subtraits as they are composed, call {@link Trait#aliases} or 427 * {@link Trait#excludes}. Calls to these functions may be chained. 428 * Passing a trait into o.uses causes the methods from that trait 429 * (plus any aliases and modulo any excludes) to be added to the new 430 * trait. If any of these exported method names conflict with another 431 * trait specified in o.uses, the conflict must be resolved (unless 432 * the conflicting method names point to the exact same function). 433 * Conflicts may be resolved by either 1) overriding the method in 434 * o.methods 2) overriding the method at the class level (if this 435 * trait is being defined as part of a class) or 3) excluding all but 436 * one of the conflicting methods. 437 * @param {String|String[]} [o.requires] A list of method names that must 438 * be implemneted before this trait can function properly. A trait 439 * may have open requirements, but all its requirements must be 440 * fulfilled when it is composed into a class. Requirements can be 441 * satisfied by other traits or class methods. 442 * @param {Object} [o.methods] A dictionary of methods that this trait 443 * exports (in addition to those composed in o.uses). Methods may 444 * access instance methods, but should not directly access instance 445 * variables. References to instance methods that are not deinfed in 446 * this trait or a composed subtrait should have their method names 447 * placed in the o.requires parameter. 448 */ 449 init: function(o) { 450 // Normalize arguments 451 this._subtraits = makeArray(o.uses); 452 this._requires = stringArrayToHash(o.requires); 453 this._exports = o.methods ? o.methods : {}; 454 455 // The set of exported methods and required methods of a trait must 456 // be disjoint 457 var method_name; 458 for (method_name in this._requires) { 459 if (!this._requires.hasOwnProperty(method_name)) continue; 460 if (this._exports.hasOwnProperty(method_name)) { 461 throw new Trait.TraitError("Trait cannot require and provide " + 462 "the same method " + method_name); 463 } 464 } 465 466 // Compose subtraits with this trait 467 var subtrait, exports, i, excludes = {}; 468 for (i = this._subtraits.length-1; i >= 0; i--) { 469 subtrait = this._subtraits[i]; 470 471 // Merge aliases into list of exports. 472 exports = merge({}, subtrait._exports); 473 if (subtrait._aliases) 474 merge(exports, subtrait._aliases); 475 476 // Note if any methods were excluded from this subtrait. If they 477 // were a method with the same name will need to be provided in this 478 // trait. We'll check this after composition finishes. Also, 479 // remove method from list of exports. 480 if (subtrait._excludes) { 481 for (method_name in subtrait._excludes) { 482 if (!subtrait._excludes.hasOwnProperty(method_name)) continue; 483 delete exports[method_name]; 484 merge(excludes, subtrait._excludes); 485 } 486 } 487 488 // Compose the subtrait's exported methods into this trait 489 for (method_name in exports) { // each exported method 490 if (!exports.hasOwnProperty(method_name)) continue; 491 // If this method name is overriden at the class level don't 492 // compose it. Overriding at the class level also resolves any 493 // potential conflicts for this method name, so we don't have to 494 // check for that. Superclass methods, however, should be 495 // overriden by trait methods, so we won't check the classes 496 // prototype here. 497 if (o._klass_prototype && 498 o._klass_properties.hasOwnProperty(method_name) && 499 typeof o._klass_properties[method_name] == "function") 500 { 501 // overriding at class level, do nothing 502 continue; 503 } 504 505 // If we've already exported a method with name method_name and 506 // that exported method is from another subtrait then we have 507 // a TraitConflict. Otherwise, if the already exported method is 508 // defined in this trait, it overrides methods with the same name 509 // from any subtrait (and hence we don't assign the subtrait's 510 // method here). An error is not thrown if the subtrait's method 511 // is the same as the one that already exists. 512 if (this._exports.hasOwnProperty(method_name)) { 513 if ((!o.methods || !o.methods.hasOwnProperty(method_name)) && 514 this._exports[method_name] != exports[method_name]) 515 { 516 throw new Trait.TraitError("Multiple subtraits provide " + 517 "method " + method_name + " creating a conflict. " + 518 "Exclude all but one of the methods or override the " + 519 "method in the trait/class to resolve."); 520 } 521 continue; 522 } 523 524 // No overrides and no conflicts so merge the subtrait's exported 525 // method into this trait. 526 this._exports[method_name] = exports[method_name]; 527 } 528 529 // Compose the subtrait's required methods into this trait 530 for (method_name in subtrait._requires) { 531 if (!subtrait._requires.hasOwnProperty(method_name)) continue; 532 if (!this._exports.hasOwnProperty(method_name)) { 533 this._requires[method_name] = true; 534 } 535 } 536 537 // Clear out alias and exclude data (which only makes sense in 538 // the context of a 'use' clause). 539 delete subtrait._aliases 540 delete subtrait._excludes 541 } 542 543 // Make sure that excluded methods have been overriden either by 544 // another trait or class 545 for (method_name in excludes) { 546 if (!excludes.hasOwnProperty(method_name)) continue; 547 if (!this._exports.hasOwnProperty(method_name) && 548 (!o._klass_prototype || 549 (typeof o._klass_prototype[method_name] != "function" && 550 typeof o._klass_properties[method_name] != "function" 551 ))) 552 { 553 throw new Trait.TraitError("Excluded method " + method_name + 554 " must be provided by another class or trait."); 555 } 556 } 557 558 // Prune any requirements that are fullfilled by the composed trait. 559 // If this trait is being defined in a class, make sure that all other 560 // requirements are fullfilled by the class. 561 for (method_name in this._requires) { 562 if (!this._requires.hasOwnProperty(method_name)) continue; 563 if (this._exports.hasOwnProperty(method_name)) { 564 delete this._requires[method_name]; 565 } else if (o._klass_prototype && 566 (typeof o._klass_prototype[method_name] != "function" && 567 typeof o._klass_properties[method_name] != "function" 568 )) 569 570 { 571 throw new Trait.TraitError("Trait requirement " + method_name + 572 " not fullfilled by class."); 573 } 574 } 575 }, 576 577 /** 578 * Alias a method to a different name during trait composition. Aliases 579 * should only be used in a 'uses' clause inside a class or trait 580 * definition. It may be chained with {@link Trait#excludes}. Aliasing 581 * a method causes it to be copied under the new alias name in addition 582 * to the original method name. Multiple aliases may be made to the 583 * same function. Aliases are treated exactly like normal method names 584 * during trait composition. 585 * 586 * @throws {Trait.TraitError} Throws a TraitError if attempting to alias a 587 * non-existent function, create an alias with the same name as a 588 * natively exported method, or create an alias with the same name 589 * as one of the required method names. 590 * @return {Trait} this 591 * @param {Object} o A String to String mapping of alias names to exported 592 * method names. 593 */ 594 aliases: function(o) { 595 this._aliases = this._aliases || {}; 596 // Check that alias targets exist, aliases don't override existing 597 // methods, and aliases/requirements are disjoint 598 for (var alias in o) { 599 if (!o.hasOwnProperty(alias)) continue; 600 if (!this._exports.hasOwnProperty(o[alias])) 601 throw new Trait.TraitError("can't alias " + alias 602 + " to " + o[alias] + 603 " because trait doesn't have that method"); 604 if (this._exports.hasOwnProperty(alias)) 605 throw new Trait.TraitError("can't create an alias with name " + 606 alias + " because trait natively exports that method"); 607 if (this._requires.hasOwnProperty(alias)) 608 throw new Trait.TraitError("can't create an alias with name " + 609 alias + " because trait requires method with same name"); 610 this._aliases[alias] = this._exports[o[alias]]; 611 } 612 return this; 613 }, 614 615 /** 616 * Exclude a method during trait composition. Excludes should only be 617 * used in a 'uses' clause inside a class or trait definition. It may be 618 * chained with {@link Trait#aliases}. Excluding a method causes it to 619 * not be copied into the containing class or trait as it normally would. 620 * If a method is excluded a method with the same name must be provided, 621 * either by another trait or a class method. 622 * 623 * @throws {Trait.TraitError} Throws a TraitError if attempting to exclude 624 * a method that is not exported by this trait. 625 * @returns {Trait} this 626 * @param {String|String[]} a Method(s) to exclude during trait 627 * composition. 628 */ 629 excludes: function(a) { 630 this._excludes = this._excludes || {}; 631 a = makeArray(a); 632 // Check that excluded methods exist 633 for (var i = a.length-1; i >=0; i--) { 634 if (!this._exports.hasOwnProperty(a[i])) { 635 throw new Trait.TraitError("can't exclude method " + a[i] + 636 " because no such method exists in trait"); 637 } 638 this._excludes[a[i]] = true; 639 } 640 return this; 641 }, 642 643 /** 644 * Inspect all traits used by this trait. Note that this trait is 645 * included in the list of what this trait 'does'. If no argument is 646 * passed, an array of all traits is returned. If a trait is passed, a 647 * boolean is returned indicating if the specified trait is one of the 648 * composed traits. This method differs from {@link Trait#subtraits} in 649 * that subtraits only checks for traits specified in the use clause, 650 * while this method recursively checks to see if any of the subtraits' 651 * subraits match, and so on. 652 * 653 * @return {Trait[]|Boolean} List of all traits composed into this trait 654 * or a boolean indicating if a particular trait was used. 655 * @param {Trait} [trait_ref] Trait to check for inclusion in the list 656 * of composed traits. 657 */ 658 does: function(trait_ref) { 659 // Computing the list of implemented traits can be a little expensive 660 // so lazily generate it on the first invocation of does 661 if (!this._does) { 662 // This trait does itself, its subtraits, and . . . 663 this._does = [this].concat(this._subtraits); 664 // . . . its subtraits' subtratis, etc.. 665 var i, j, subsub; 666 for (i = this._subtraits.length-1; i >= 0; i--) { 667 subsub = this._subtraits[i].does(); 668 // Since the same trait can be acquired from multiple sources, 669 // need to check traits for uniqueness before adding to _does. 670 // This would be more efficient with a dictionary but JS objects 671 // don't support pointer/reference keys, and I have a feeling that 672 // a hand rolled dictionary implementation is going to be slooow. 673 for (j = subsub.length-1; j >= 0; j--) { 674 if (this._does.indexOf(subsub[j]) === -1) 675 this._does.push(subsub[j]); 676 } 677 } 678 } 679 680 if (trait_ref) 681 return this._does.indexOf(trait_ref) >= 0; 682 return this._does; 683 }, 684 685 /** 686 * Inspect method names required by this trait. If no arguments are 687 * passed, an object with keys representing all the required methods is 688 * returned. If a string argument is given, requires returns a boolean 689 * indicating if this trait requires a method with that name. 690 * 691 * @return {Object|Boolean} Object keyed by required method names or 692 * boolean indicating if a particular method name is required. 693 * @param {String} method_name Method name to check if in required method 694 * name list. 695 */ 696 requires: function(method_name) { 697 if (method_name) 698 return this._requires.hasOwnProperty(method_name) && 699 this._requires[method_name]; 700 return this._requires; 701 }, 702 703 /** 704 * Inspect subtraits used by this trait. Note that only immediate 705 * subtraits are dealt with here (i.e. those passed in the 'uses' 706 * clause). To recursively check if a trait uses another trait see 707 * {@link Trait#does}. If no argument is passed, an array of all 708 * subtraits is returned. If a trait is passed, a boolean is returned 709 * indicating if the specified trait is one of the subtraits. 710 * 711 * @return {Trait[]|Boolean} List of all subtraits or boolean indicating 712 * if a particular subtrait was used. 713 * @param {Trait} [trait_ref] Trait to check for inclusion in the list 714 * of subtraits. 715 */ 716 subtraits: function(trait_ref) { 717 if (trait_ref) 718 return this._subtraits.indexOf(trait_ref) >= 0; 719 return this._subtraits; 720 }, 721 722 /** 723 * Inspect methods exported by this trait. If no arguments are passed, 724 * an object mapping each method name exported by this trait to its 725 * associated function is returned. If a string argument is given, 726 * methods checks if this trait exports a method with that name. If so 727 * it returns the associated function, otherwise it returns undefined. 728 * 729 * @return {Object|Function} Mapping of method names to functions, a 730 * specific function, or undefined. 731 * @param {String} [method_name] Name of the method to look up in this 732 * trait's method export list. 733 */ 734 methods: function(method_name) { 735 if (method_name) 736 return this._exports.hasOwnProperty(method_name) && 737 this._exports[method_name]; 738 return this._exports; 739 } 740 } 741 }); 742 743 /** 744 * Factory method to create new traits. Arguments are the same as those 745 * passed to the {@link Trait} constructor. This static method is the 746 * preferred way to create new traits. 747 * 748 * @example 749 * var TColoredCircle = Trait.define({ 750 * uses: [TColor, TCircle.aliases({'drawOutline': 'draw'})], 751 * requires: 'fillWithColor', 752 * methods: { 753 * draw: function() { 754 * // draw a colored circle 755 * this.drawOutline(); 756 * this.fillWithColor(this.getColor()); 757 * } 758 * } 759 * }); 760 * 761 * @static 762 * @memberOf Trait 763 * @name define 764 * @function 765 * @see Trait 766 * @return {Trait} 767 * @throws {Trait.TraitError} 768 * @param {Object} o 769 * @param {Trait|Trait[]} [o.uses] 770 * @param {String|String[]} [o.requires] 771 * @param {Object} [o.methods] 772 */ 773 Trait.define = function(o) { 774 return new Trait(o); 775 }; 776 777 Trait.TraitError = Class.define({ 778 superclass: Error, 779 members: /** @lends Trait.TraitError.prototype */ { 780 /** 781 * Generic error thrown for any trait related exceptions. 782 * 783 * @constructs 784 * @augments Error 785 * @param {String} msg The message to show when printing out the string. 786 */ 787 init: function(msg) { 788 this.name = "TraitError"; 789 this.message = msg; 790 } 791 } 792 }); 793 })(); 794