1 //############################################################################## 2 // The $class Library 3 // Copyright 2006, Jeff Lau 4 // License: http://creativecommons.org/licenses/LGPL/2.1/ 5 // Contact: jlau@uselesspickles.com 6 // Web: www.uselesspickles.com 7 //############################################################################## 8 9 /** 10 * @fileOverview 11 * <p> 12 * This is the debug version of the $class library, which contains tons of useful error checking. Include this 13 * file during development or debugging. When it's time to deploy, be sure to switch over to including class.js 14 * to get rid of the error checking overhead. 15 * </p> 16 * 17 * <p> 18 * <h2> 19 * Error/Warning Logging 20 * </h2> 21 * The debug version of the $class library attempts to log warning and error 22 * messages by methods "console.warn" and "console.error". These are logging 23 * methods supported by the Firebug plugin for the Firefox web browser. If you 24 * are debugging in a different environment and would like to log warnings/errors 25 * in some way, create a global named "console" with methods named "warn" and 26 * "error". These methods should expect to receive a single argument and should 27 * log/display the value in whatever way is appropriate for your logging needs. 28 * The logging methods should be defined before any calls to the $class 29 * library to ensure all warnings are logged. 30 * 31 * Example: 32 <pre class="brush:js"> 33 var console = { 34 warn: function(message) { 35 alert("Warning: " + message); 36 }, 37 38 error: function(message) { 39 alert("Error: " + message); 40 } 41 }; 42 </pre> 43 * </p> 44 */ 45 46 /** 47 * @namespace 48 * This is where the $class library stores some of its implementation details to avoid polluting the global 49 * scope. 50 */ 51 $class_library = { 52 /** 53 * The version of the $class library. 54 * @type String 55 */ 56 version: "2.0b", 57 58 /** 59 * True if this is the debug version of the library. 60 * @type Boolean 61 */ 62 debug: true, 63 64 _globalObject: this, 65 66 _hasOwnProperty: Object.prototype.hasOwnProperty ? 67 function (obj, prop) { 68 return obj.hasOwnProperty(prop); 69 } 70 : 71 function (obj, prop) { 72 return (prop in obj) && obj.constructor.prototype[prop] !== obj[prop]; 73 }, 74 75 _warn: function() { 76 var global = this._globalObject; 77 78 if (global.console && global.console.warn) { 79 var apply = Function.prototype.apply; 80 apply.call(global.console.warn, global.console, arguments); 81 } 82 }, 83 84 _error: function() { 85 var global = this._globalObject; 86 87 if (global.console && global.console.error) { 88 var apply = Function.prototype.apply; 89 apply.call(global.console.error, global.console, arguments); 90 } 91 92 var message = ""; 93 94 for (var i = 0; i < arguments.length; ++i) { 95 if (i) { 96 message += "; "; 97 } 98 99 message += arguments[i]; 100 } 101 102 throw new Error(message); 103 }, 104 105 _copyObject: function(obj1, obj2) { 106 var result = (arguments.length == 2 && obj1) ? obj1 : {}; 107 var source = ((arguments.length == 2) ? obj2 : obj1) || {}; 108 109 for (var i in source) { 110 result[i] = source[i]; 111 } 112 113 return result; 114 }, 115 116 _surrogateCtor: function() { 117 } 118 }; 119 120 //############################################################################## 121 122 /** 123 * <p> 124 * Creates a namespace. Note that this documentation describes the behavior of $namespace when used as a method 125 * (without the "new" keyword). This should only be called as a method rather than as a constructor. Instances 126 * of $namespace are created as a private implementation detail of $namespace being called as a method. 127 * </p> 128 * <p> 129 * A namespace is simply a named "container" for classes, methods, properties or even other namespaces. 130 * </p> 131 * 132 * <p> 133 * <h2>Example:</h2> 134 <pre class="brush:js"> 135 $namespace("MyLibrary.UI.Forms"); 136 137 MyLibrary.UI.Forms.VERSION = 5; 138 139 $class("MyLibrary.UI.Forms.Field", { 140 // ... properties, methods, etc. 141 }); 142 143 var field = new MyLibrary.UI.Forms.Field(); 144 </pre> 145 * </p> 146 * 147 * @class 148 * <p> 149 * This class represents a namespace, but its constructor is used as a function (without the "new" 150 * keyword) to create a namespace. It is used as a constructor for private implementation purposes only. 151 * </p> 152 * 153 * @constructor 154 * 155 * @param {String} name The name of the namespace. The name may describe a path of multiple nested 156 * namespace/class/object names separated by a period (.). All namespaces along the path will be created as 157 * necessary. For example, a namespace name of "MyLibrary.UI.Forms" will create a namespace named "MyLibrary" 158 * that contains a namespace named "UI" which in turn contains a namespace named "Forms". If any of the 159 * namespaces in the specified name path already exist, but were not created by $namespace, a warning will be 160 * issued by the debug version of the $class library. 161 * 162 * @return {$namespace} the newly created (or pre-existing) namespace. If an entire path of namespaces was 163 * created, only the last namespace in the path is returned. 164 */ 165 function $namespace(name) { 166 // if being called as a constructor... 167 if (this instanceof $namespace) { 168 /** 169 * Overridden to return a string that identifies this namespace. This may be useful for debugging purposes. 170 * This also gives each namespace a unique 171 * string representation for use as object keys. 172 * @return {String} a string in the format "[$namespace _name_]", where _name_ is the name of the 173 * namespace, including the full path of parent namespaces. 174 */ 175 this.toString = function() { 176 return "[$namespace " + name + "]"; 177 }; 178 179 $class_library._surrogateCtor.prototype = this; 180 return new $class_library._surrogateCtor(); 181 } 182 183 var components = name.split("."); 184 var context = $class_library._globalObject; 185 186 for (var i = 0; i < components.length; ++i) { 187 var nextContext = context[components[i]]; 188 189 if (!nextContext) { 190 nextContext = new $namespace(components.slice(0, i + 1).join(".")); 191 context[components[i]] = nextContext; 192 } else if (!(nextContext instanceof $namespace)) { 193 $class_library._warn("Warning in $namespace(\"" + name + "\"): [" + 194 components.slice(0, i + 1).join(".") + 195 "] is already defined, but is not a namespace."); 196 } 197 198 context = nextContext; 199 } 200 201 return context; 202 } 203 204 //############################################################################## 205 206 /** 207 * <p> 208 * Creates a class with the specified name and properties. Note that this documentation describes the behavior 209 * of $class when used as a method (without the "new" keyword). This should only be called as a method rather 210 * than as a constructor. Instances of $class are created as a private implementation detail of $class being 211 * called as a method. 212 * </p> 213 * 214 * <p> 215 * $class supports many features, such as abstract methods, implementing interfaces, easily calling an 216 * overridden implementation of a method, and much more. See {@link $class.descriptor} for details. 217 * </p> 218 * 219 * <p> 220 * The new class will have a static property named "$class" that is an instance of $class and can provide 221 * information about the class that was created by $class. Should I say "class" a few more times? 222 * </p> 223 * 224 * <p> 225 * <h2>Example:</h2> 226 <pre class="brush:js"> 227 $class("Sample", { 228 $constructor: function(arg) { 229 this._property = arg; 230 }, 231 232 getProperty: function() { 233 return this._property; 234 } 235 }); 236 237 var sample = new Sample("Hello World!"); 238 239 alert(sample.getProperty()); // alerts "Hello World" 240 241 alert(sample instanceof Sample); // alerts "true" 242 </pre> 243 * </p> 244 * 245 * @class This class encapsulates information about a class, but its constructor is used as a function 246 * (without the "new" keyword) to create a class. It is used as a constructor for private implementation 247 * purposes only. Each class created by $class has a static property named "$class" that is an instance 248 * of $class. $class instances can also be obtained by calling {@link #getClass}. 249 * 250 * @constructor 251 * 252 * @param {String} name The name of the class. The name may describe a path of multiple nested 253 * namespace/class/object names separated by a period (.). The entire path to the class name is expected 254 * to exist already. See {@link $namespace} for details about creating namespaces. 255 * 256 * @param {$class.descriptor} descriptor An object containing the properties of the class, which can describe 257 * methods, static methods, static properties, and more. 258 * 259 * @return {Function} The constructor for the newly created class. 260 */ 261 function $class(name, descriptor) { 262 // if being called as a constructor... 263 if (this instanceof $class) { 264 if (!descriptor.calledFrom$class && !descriptor.calledFrom$class_adapt) { 265 $class._error(name, "Do not directly instantiate $class! Call $class as a function instead."); 266 } 267 268 this._name = name; 269 this._isNative = descriptor.isNative; 270 this._ctor = descriptor.ctor; 271 this._baseCtor = descriptor.baseCtor || (this._ctor == Object ? null : Object); 272 this._isFinal = Boolean(descriptor.isFinal); 273 this._isSingleton = Boolean(descriptor.isSingleton); 274 this._isMultiton = Boolean(descriptor.isMultiton); 275 this._isAbstract = false; 276 this._interfaces = descriptor.interfaces || {}; 277 this._abstractMethods = {}; 278 this._finalMethods = {}; 279 this._interfaceMethods = {}; 280 this._children = []; 281 282 // if not creating $class for Object... 283 if (this._ctor != Object) { 284 // if inheriting something other than Object... 285 if (this._baseCtor != Object) { 286 // if this $class object is being created internally by the $class function... 287 if (descriptor.calledFrom$class) { 288 // a 'surrogate' constructor used to create inheritance relationship 289 // without actually invoking the base class's constructor code 290 $class_library._surrogateCtor.prototype = this._baseCtor.prototype; 291 this._ctor.prototype = new $class_library._surrogateCtor(); 292 this._ctor.prototype.constructor = this._ctor; 293 } 294 295 // inherit info about the base class 296 var base$class = this._baseCtor.$class; 297 298 if (base$class) { 299 this._interfaces = $class_library._copyObject(base$class._interfaces); 300 this._abstractMethods = $class_library._copyObject(base$class._abstractMethods); 301 this._finalMethods = $class_library._copyObject(base$class._finalMethods); 302 303 base$class._children.push(this); 304 } else { 305 this._interfaces = {}; 306 this._abstractMethods = {}; 307 this._finalMethods = {}; 308 } 309 } 310 311 // store this class info on the prototype 312 this._ctor.prototype._$class = this; 313 } 314 315 // Storing class info on prototype and constructor causes an IE leak; store created $class objects to clean up when 316 // unloading the page. 317 $class._cleanupCache.push(this); 318 return; 319 } 320 321 if ($class_library._hasOwnProperty(descriptor, "$extends") && !descriptor.$extends) { 322 $class._propertyError(name, "$extends", "Cannot extend null. You probably forgot to include the JS file containing the declaration of the parent class."); 323 } 324 325 var baseCtor = descriptor.$extends || Object; 326 var isSingleton = !!descriptor.$singleton; 327 var isMultiton = !!descriptor.$multiton; 328 329 if (!(baseCtor instanceof Function)) { 330 $class._propertyError(name, "$extends", "Must be a constructor function"); 331 } 332 333 if (!baseCtor.$class) { 334 $class._propertyWarn(name, "$extends", "Extending a class that has no $class information. " 335 + "The class has been automatically adapted and given the name " 336 + "\"" + name + "$base\". Use $class.adapt() to pre-adapt " 337 + "the class and provide the correct name"); 338 $class.adapt(baseCtor, name + "$base"); 339 } 340 341 if (baseCtor.$class && baseCtor.$class._isFinal) { 342 $class._propertyError(name, "$extends", "Cannot extend [" + baseCtor.$class.getName() + "] because its constructor is declared $final."); 343 } 344 345 if (!isSingleton && baseCtor.$class && baseCtor.$class._isSingleton) { 346 $class._propertyError(name, "$extends", "Cannot extend [" + baseCtor.$class.getName() + "] because it is a singleton. Singletons can only be extended by other singletons."); 347 } 348 349 if (!isMultiton && baseCtor.$class && baseCtor.$class._isMultiton) { 350 $class._propertyError(name, "$extends", "Cannot extend [" + baseCtor.$class.getName() + "] because it is a multiton. Multitons can only be extended by other multitons."); 351 } 352 353 if (isSingleton && isMultiton) { 354 $class._propertyError(name, "$singleton/$multiton", "Cannot be both a singleton and a multiton"); 355 } 356 357 var ctorName = name.replace(/[^.]*\./g, ""); 358 var ctor = descriptor.$constructor || descriptor[ctorName]; 359 var isFinal = false; 360 361 if (ctor instanceof $class._ModifiedProperty && ctor.getModifier() == "final") { 362 ctor = ctor.getValue(); 363 isFinal = true; 364 } 365 366 if (!ctor) { 367 $class_library._warn("No constructor specified for class [" + name + "]; using an empty default constructor."); 368 ctor = new Function(); 369 } 370 371 if (!(ctor instanceof Function)) { 372 $class._propertyError("$constructor", "Must be a function"); 373 } 374 375 ctor = $class._wrapCtorMethod(ctor, baseCtor); 376 ctor.$class = new $class(name, {ctor:ctor, baseCtor:baseCtor, isFinal:isFinal, isSingleton:isSingleton, isMultiton:isMultiton, calledFrom$class:true}); 377 378 if (isSingleton) { 379 var createInstance = (descriptor.$singleton.createInstance instanceof Function) ? descriptor.$singleton.createInstance : $class._createSingletonInstance_implementation; 380 381 descriptor._$class_createSingletonInstance = $static(createInstance); 382 descriptor._$class_singletonInstance = $static(null); 383 descriptor.getInstance = $static($class._getSingletonInstance_implementation); 384 } 385 386 if (isMultiton) { 387 var createInstance = (descriptor.$multiton.createInstance instanceof Function) ? descriptor.$multiton.createInstance : $class._createMultitonInstance_implementation; 388 var createCacheKey = (descriptor.$multiton.createCacheKey instanceof Function) ? descriptor.$multiton.createCacheKey : $class._createMultitonCacheKey_implementation; 389 390 descriptor._$class_createMultitonInstance = $static(createInstance); 391 descriptor._$class_createMultitonCacheKey = $static(createCacheKey); 392 descriptor._$class_multitonInstances = $static({}); 393 descriptor._$class_creatingMultitonInstance = $static({}); 394 descriptor.getInstance = $static($class._getMultitonInstance_implementation); 395 } 396 397 // implement interfaces 398 if (descriptor.$implements != null) { 399 var ifaces = descriptor.$implements; 400 401 // convert to an array if it is a single object 402 if (!(ifaces instanceof Array)) { 403 ifaces = [ifaces]; 404 } 405 406 for (var i = 0, ifacesLength = ifaces.length; i < ifacesLength; ++i) { 407 // make sure the 'interface' to extend is really an interface 408 if (!(ifaces[i] instanceof $interface)) { 409 $class._propertyError(name, "$implements", "$interface or array of $interfaces expected"); 410 } 411 412 ctor.$class._implement(ifaces[i]); 413 } 414 } 415 416 var specialProperties = {$constructor:true,$singleton:true,$multiton:true,$extends:true,$implements:true,$static:true}; 417 var processedProperties = {}; 418 specialProperties[ctorName] = true; 419 420 // process all properties in the class descriptor 421 for (var propertyName in descriptor) { 422 // skip over special properties 423 if ($class_library._hasOwnProperty(specialProperties, propertyName)) { 424 continue; 425 } 426 427 ctor.$class._processProperty(propertyName, descriptor[propertyName]); 428 processedProperties[propertyName] = true; 429 } 430 431 if (!$class_library._hasOwnProperty(processedProperties, "toString") && $class_library._hasOwnProperty(descriptor, "toString")) { 432 ctor.$class._processProperty("toString", descriptor["toString"]); 433 } 434 435 if (!$class_library._hasOwnProperty(processedProperties, "valueOf") && $class_library._hasOwnProperty(descriptor, "valueOf")) { 436 ctor.$class._processProperty("valueOf", descriptor["valueOf"]); 437 } 438 439 // collect names of all interface methods that are not implemented 440 var missingInterfaceMethods = ctor.$class._getMissingInterfaceMethods(); 441 442 // if any interface methods are not implemented, we have a problem 443 if (missingInterfaceMethods.length != 0) { 444 var message = "The following interface methods must be implemented: "; 445 446 for (var i = 0; i < missingInterfaceMethods.length; ++i) { 447 message += "[" + missingInterfaceMethods[i] + "] "; 448 } 449 450 $class._error(name, message); 451 } 452 453 // if any abstract methods are remaining, this class is abstract 454 for (var methodName in ctor.$class._abstractMethods) { 455 ctor.$class._isAbstract = true; 456 break; 457 } 458 459 // set default toString method 460 if (ctor.prototype.toString == Object.prototype.toString) { 461 ctor.prototype.toString = new Function( 462 "return \"[object \" + $class.typeOf(this) + \"]\";" 463 ); 464 } 465 466 // store the constructor so it is accessible by the specified name 467 try { 468 var result = eval(name + " = ctor;"); 469 } catch (error) { 470 $class._error(name, "Invalid class name: " + error.message); 471 } 472 473 // call the static initializer 474 if (descriptor.$static instanceof Function) { 475 try { 476 descriptor.$static.call(ctor); 477 } catch (error) { 478 $class._error(name, "Error while executing static initializer: " + error.message); 479 } 480 } else if (descriptor.$static != null) { 481 $class._propertyError(name, "$static", "function expected"); 482 } 483 484 return result; 485 } 486 487 //############################################################################## 488 489 $class.prototype = { 490 //############################################################################ 491 492 /** 493 * Gets the full name of the class. 494 * @return {String} the full name of the class. 495 */ 496 getName: function() { 497 return this._name; 498 }, 499 500 //############################################################################ 501 502 /** 503 * Returns true if this class is one of the standard JavaScript classes (Number, Function, etc). 504 * @return {Boolean} true if this class is one of the standard JavaScript classes. 505 */ 506 isNative: function() { 507 return this._isNative; 508 }, 509 510 //############################################################################ 511 512 /** 513 * Returns the prototype object of this class. 514 * @return {Object} The class's prototype object. 515 */ 516 getPrototype: function() { 517 return this._ctor.prototype; 518 }, 519 520 //############################################################################ 521 522 /** 523 * Gets the constructor for the class. 524 * @return {Function} the constructor for the class. 525 */ 526 getConstructor: function() { 527 return this._ctor; 528 }, 529 530 //############################################################################ 531 532 /** 533 * Gets the $class object of this class's parent class. 534 * @return {$class} The $class object for this class's parent class. Returns null if this class is Object. 535 */ 536 getSuperclass: function() { 537 return this._baseCtor ? this._baseCtor.$class : null; 538 }, 539 540 //############################################################################ 541 542 /** 543 * Tests if an object is an instance of this class. 544 * @param {Object} obj 545 * @return {Boolean} true if the object is an instance of this class, or any of its child classes. 546 */ 547 isInstance: function(obj) { 548 return $class.instanceOf(obj, this._ctor); 549 }, 550 551 //############################################################################ 552 553 /** 554 * Tests if this class implements a specific interface. 555 * @param {$interface} iface 556 * @return {Boolean} true if this class implements the interface. 557 */ 558 implementsInterface: function(iface) { 559 return $class_library._hasOwnProperty(this._interfaces, iface.getName()); 560 }, 561 562 //############################################################################ 563 564 /** 565 * Overridden to return a string that identifies this $class object. 566 * @return {String} a string in the form "[$class _name_]", where _name_ is the full name of this class. 567 */ 568 toString: function() { 569 return "[$class " + this._name + "]"; 570 }, 571 572 //############################################################################ 573 574 _implement: function(iface) { 575 $class_library._copyObject(this._interfaces, iface._interfaces); 576 $class_library._copyObject(this._interfaceMethods, iface._methods); 577 }, 578 579 //############################################################################ 580 581 _processProperty: function(propertyName, value) { 582 var modifier = ""; 583 584 if (value instanceof $class._ModifiedProperty) { 585 modifier = value.getModifier(); 586 value = value.getValue(); 587 588 if (value instanceof $class._ModifiedProperty) { 589 $class._propertyError(name, propertyName, "Only one modifier may be used per value"); 590 } 591 592 switch (modifier) { 593 case "static": 594 this._ctor[propertyName] = value; 595 return; 596 597 case "abstract": 598 case "final": 599 if (!(value instanceof Function)) { 600 $class._propertyError(name, propertyName, "The $" + modifier + " modifier may only be applied to function values."); 601 } 602 break; 603 } 604 } 605 606 // don't override anything that is final 607 if ($class_library._hasOwnProperty(this._finalMethods, propertyName)) { 608 var className = this._finalMethods[propertyName]; 609 $class._propertyError(this._name, propertyName, "Cannot override a final property (originally declared final in class [" + className + "])"); 610 } 611 612 var isAbstract = false; 613 614 switch (modifier) { 615 case "final": 616 this._finalMethods[propertyName] = this._name; 617 break; 618 619 case "abstract": 620 this._abstractMethods[propertyName] = this._name; 621 value = $class._createAbstractMethod(this._name, propertyName); 622 isAbstract = true; 623 break; 624 } 625 626 // if the property is overriding one from an inherited class... 627 if (propertyName in this._ctor.prototype) { 628 var baseValue = this._ctor.prototype[propertyName]; 629 630 // only allow functions to be overridden 631 if (!(baseValue instanceof Function) || !(value instanceof Function)) { 632 $class._propertyError(this._name, propertyName, "Only function properties can be overridden (with another function)"); 633 } else { 634 if ($class._uses$base.test(value) && !$class_library._hasOwnProperty(this._abstractMethods, propertyName)) { 635 // wrap the method to give it access to the special $base property 636 value = $class._wrapExtendedMethod(value, baseValue); 637 } 638 } 639 640 if (!isAbstract) { 641 delete this._abstractMethods[propertyName]; 642 } 643 } 644 645 this._ctor.prototype[propertyName] = value; 646 }, 647 648 //############################################################################ 649 650 _getMissingInterfaceMethods: function() { 651 var result = new Array(); 652 653 for (var methodName in this._interfaceMethods) { 654 if (!(this._ctor.prototype[methodName] instanceof Function)) { 655 var ifaceName = this._interfaceMethods[methodName]; 656 result.push(ifaceName + "." + methodName); 657 } 658 } 659 660 // don't need this info any more 661 delete this._interfaceMethods; 662 663 return result; 664 } 665 666 //############################################################################ 667 }; 668 669 //############################################################################## 670 // List of $class objects created so they can be cleaned up when unloading the page 671 $class._cleanupCache = []; 672 673 // using attachEvent because this is only needed to avoid memory leaks in IE, and only IE supports attachEvent. 674 if (typeof window != "undefined" && window.attachEvent) { 675 window.attachEvent('onunload', function() { 676 for (var i=0,clz; clz = $class._cleanupCache[i]; i++) { 677 delete clz._ctor.prototype._$class; 678 delete clz._ctor.$class; 679 } 680 }); 681 } 682 683 //############################################################################## 684 685 $class._uses$base = /\bthis\.\$base\b/; 686 687 //############################################################################## 688 689 $class._error = function(name, message) { 690 $class_library._error("Error in $class(\"" + name + "\"" + ", ...): " + message); 691 }; 692 693 //############################################################################## 694 695 $class._propertyError = function(name, propertyName, message) { 696 $class_library._error("Error in $class(\"" + name + "\"" + ", ...), property [" + propertyName + "]: " + message); 697 }; 698 699 //############################################################################## 700 701 $class._propertyWarn = function(name, propertyName, message) { 702 $class_library._warn("Warning in $class(\"" + name + "\"" + ", ...), property [" + propertyName + "]: " + message); 703 }; 704 705 //############################################################################## 706 707 $class._wrapCtorMethod = function(method, baseMethod) { 708 // automatically call the base class constructor if inheriting something 709 // other than Object and the constructor does not already call it 710 var call$base = !$class._uses$base.test(method) && baseMethod != Object; 711 var result = $class._createCtorWrapper(call$base); 712 713 result._$class_wrappedMethod = method; 714 result.toString = $class._wrappedMethod_toString; 715 716 return result; 717 }; 718 719 //############################################################################## 720 721 $class._createCtorWrapper = function(call$base) { 722 return function() { 723 var method = arguments.callee; 724 var ctor = $class.getClass(this).getConstructor(); 725 726 // if this is the actual constructor for the object (not a base class constructor)... 727 if (ctor == method) { 728 if (method.$class._isAbstract) { 729 var message = "Attempted instantiation of the abstract class [" + 730 method.$class._name + "]. Abstract methods: "; 731 732 for (var methodName in method.$class._abstractMethods) { 733 message += "[" + method.$class._abstractMethods[methodName] + "." + 734 methodName + "] "; 735 } 736 737 $class_library._error(message); 738 } 739 740 if (ctor.$class._isSingleton && !ctor._$class_creatingSingletonInstance) { 741 var message = "Attempted instantiation of the singleton class [" + 742 ctor.$class._name + "]. Use " + ctor.$class._name + ".getInstance()"; 743 744 $class_library._error(message); 745 } 746 747 if (ctor.$class._isMultiton && !ctor._$class_creatingMultitonInstance[ctor._$class_createMultitonCacheKey.apply(ctor, arguments)]) { 748 var message = "Attempted instantiation of the multiton class [" + 749 ctor.$class._name + "]. Use " + ctor.$class._name + ".getInstance([constructor args])"; 750 751 $class_library._error(message); 752 } 753 } 754 755 var previousBase = this.$base; 756 this.$base = method.$class._baseCtor; 757 758 try { 759 if (call$base) { 760 this.$base.apply(this, arguments); 761 } 762 763 return method._$class_wrappedMethod.apply(this, arguments); 764 } finally { 765 this.$base = previousBase; 766 } 767 }; 768 }; 769 770 //############################################################################## 771 772 $class._wrapExtendedMethod = function(method, baseMethod) { 773 var result = $class._createExtendedMethodWrapper(); 774 775 result._$class_wrappedMethod = method; 776 result._$class_baseMethod = baseMethod; 777 result.toString = $class._wrappedMethod_toString; 778 779 return result; 780 }; 781 782 //############################################################################## 783 784 $class._createExtendedMethodWrapper = function() { 785 return function() { 786 var method = arguments.callee; 787 788 var previousBase = this.$base; 789 this.$base = method._$class_baseMethod; 790 791 try { 792 return method._$class_wrappedMethod.apply(this, arguments); 793 } finally { 794 this.$base = previousBase; 795 } 796 }; 797 }; 798 799 //############################################################################## 800 801 $class._wrappedMethod_toString = function() { 802 return this._$class_wrappedMethod.toString(); 803 }; 804 805 //############################################################################## 806 807 $class._createAbstractMethod = function(name, propertyName) { 808 return function() { 809 $class_library._error("The abstract method [" + propertyName + "] declared " + 810 "by class [" + name + "] was invoked on an object of type [" + 811 $class.typeOf(this) + "]."); 812 }; 813 }; 814 815 //############################################################################## 816 817 $class._getSingletonInstance_implementation = function() { 818 if (this._$class_singletonInstance) { 819 return this._$class_singletonInstance; 820 } 821 822 if (this._$class_creatingSingletonInstance) { 823 $class_library._error(this.$class.getName() + ".getInstance(): infinite recursion encountered. Check for a call to getInstance that is called as a result of instantiating this class."); 824 } 825 826 this._$class_creatingSingletonInstance = true; 827 this._$class_singletonInstance = this._$class_createSingletonInstance(); 828 delete this._$class_creatingSingletonInstance; 829 830 return this._$class_singletonInstance; 831 }; 832 833 //############################################################################## 834 835 $class._createSingletonInstance_implementation = function() { 836 return new this(); 837 }; 838 839 //############################################################################## 840 841 $class._getMultitonInstance_implementation = function() { 842 var cacheKey = this._$class_createMultitonCacheKey.apply(this, arguments); 843 var result = this._$class_multitonInstances[cacheKey]; 844 845 if (result) { 846 return result; 847 } 848 849 if ($class_library._hasOwnProperty(this._$class_creatingMultitonInstance, cacheKey)) { 850 $class_library._error(this.$class.getName() + ".getInstance(): infinite recursion encountered. Check for a call to getInstance that is called as a result of instantiating this class."); 851 } 852 853 this._$class_creatingMultitonInstance[cacheKey] = true; 854 result = this._$class_multitonInstances[cacheKey] = this._$class_createMultitonInstance.apply(this, arguments); 855 delete this._$class_creatingMultitonInstance[cacheKey]; 856 857 return result; 858 }; 859 860 //############################################################################## 861 862 $class._createMultitonInstance_implementation = function() { 863 return $class.instantiate(this, arguments); 864 }; 865 866 //############################################################################## 867 868 $class._createMultitonCacheKey_implementation = function() { 869 return Array.prototype.join.call(arguments, "|"); 870 }; 871 872 //############################################################################## 873 874 $class._ModifiedProperty = function(modifier, value) { 875 this._modifier = modifier; 876 this._value = value; 877 }; 878 879 $class._ModifiedProperty.prototype = { 880 getModifier: function() { 881 return this._modifier; 882 }, 883 884 getValue: function() { 885 return this._value; 886 } 887 }; 888 889 //############################################################################## 890 891 /** 892 * <p> 893 * Adapts a pre-existing class into the $class library framework. Use this to give a pre-existing class a 894 * {@link $class} object so that it can fully participate in $class library features. For example, retroactively 895 * applied interfaces ({@link $interface#applyInterface}) will be automatically applied to all descendents 896 * of a class only if that class was defined by the $class function, or if the class was adapted with this method. 897 * </p> 898 * 899 * <p> 900 * If the specified class extends another class that does not exist within the $class framework, then that parent 901 * class will be automatically adapted, but with a name that is simply the specified name with "$base" appended 902 * (there's no way for me to know the actual name of the parent class). If you want each class in the heirarchy 903 * to have the correct name, then you must adapt each class, starting from the base class. 904 * </p> 905 * 906 * <p> 907 * Note that it is not necessary to adapt a class if you simply want to extend it using the $class library. When 908 * extending a class, it will be automatically adapted with a name made up of the child class's name with "$base" 909 * appended. 910 * </p> 911 * 912 * <p> 913 * If any {@link $interface}s had been applied ({@link $interface#applyInterface}) to the class or its prototype 914 * before being adapted, it will be detected when adapting the class so that the adapted class and all of its 915 * decendents will be considered to implement the interfaces. 916 * </p> 917 * 918 * <p> 919 * <h2>Example:</h2> 920 <pre class="brush:js"> 921 var MyNamespace = {}; 922 923 MyNamespace.Sample = function(param) { 924 this._param = param; 925 } 926 927 MyNamespace.Sample.prototype = { 928 getParam: function() { 929 return 930 } 931 }; 932 933 $class.adapt(MyNamespace.Sample, "MyNamespace.Sample"); 934 935 alert(MyNamespace.Sample.$class.getName()); // alerts "MyNamespace.Sample" 936 </pre> 937 * </p> 938 * 939 * <p> 940 * <h2>Debug version warnings:</h2> 941 * <ul> 942 * <li>No name is specified; an anonymous name will be supplied.</li> 943 * <li>Base class of the adapted class is automatically adapted.</li> 944 * </ul> 945 * </p> 946 * 947 * @function 948 * @param {Function} ctor The constructor function of the class to be adapted. 949 * @param {String} name (optional) The full name of the class. If not specified, an anonymous name will be 950 * generated. 951 * @return {Boolean} true if the class was adapted, false if the class is already within the $class library 952 * framework at the time this method was called. 953 */ 954 $class.adapt = (function() { 955 var unnamedCount = 0; 956 957 return function(ctor, name, isNative) { 958 if (ctor.$class) { 959 return false; 960 } 961 962 var baseCtor = ctor.prototype.constructor; 963 964 if (ctor != Object) { 965 var hold = ctor.prototype.constructor; 966 967 if (delete ctor.prototype.constructor) { 968 baseCtor = ctor.prototype.constructor; 969 ctor.prototype.constructor = hold; 970 } 971 } 972 973 if (!name) { 974 name = "$unnamed_adapted_class_" + (unnamedCount++); 975 976 $class_library._warn("Warning in $class.adapt([function]): " 977 + "No name was specified. " 978 + "Defaulting the name to \"" + name + "\"."); 979 } 980 981 if (baseCtor == ctor || !baseCtor) { 982 baseCtor = Object; 983 } else if (!baseCtor.$class) { 984 $class_library._warn("Warning in $class.adapt([function], \"" + name + "\"): " 985 + "The class being adapted extends a class that must also be adapted. " 986 + "It will be given the name \"" + name + "$base\". " 987 + "Pre-adapt the base class to give it the correct name."); 988 $class.adapt(baseCtor, name + "$base"); 989 } 990 991 var interfaces = ctor.prototype._$class_interfaces; 992 delete ctor.prototype._$class_interfaces; 993 994 ctor.$class = new $class(name, { 995 ctor: ctor, 996 baseCtor: baseCtor, 997 isNative: isNative, 998 interfaces: interfaces, 999 calledFrom$class_adapt: true 1000 }); 1001 1002 return true; 1003 }; 1004 })(); 1005 1006 //############################################################################## 1007 1008 /** 1009 * <p> 1010 * Gets the value of a fully qualified name, possibly nested within one or more {@link $namespace}s or 1011 * namespace-like objects. Does not cause an error if any object along the path is undefined/null. Simply 1012 * returns undefined of the specified name could not be reached. 1013 * </p> 1014 * 1015 * <p> 1016 * <h2>Example:</h2> 1017 <pre class="brush:js"> 1018 $namespace("nested.namespace"); 1019 1020 nested.namespaces.SOME_VALUE = 10; 1021 1022 alert($class.resolve("nested.namespace.SOME_VALUE")) // alerts "10" 1023 1024 alert($class.resolve("nonexistant.namespace.SOME_VALUE")) // alerts "undefined" 1025 </pre> 1026 * </p> 1027 * 1028 * @param {String} name The fully qualified name of some object/value. 1029 * @return {mixed} the value found at the specified name, or undefined if it could not be reached. 1030 */ 1031 $class.resolve = function(name) { 1032 var components = name.split("."); 1033 var context = $class_library._globalObject; 1034 1035 for (var i = 0; i < components.length; ++i) { 1036 if (context == null) { 1037 $class_library._warn("$class.resolve(\"" + name + "\") failed: " 1038 + components.slice(0, i).join(".") + " is null/undefined."); 1039 return undefined; 1040 } 1041 1042 var context = context[components[i]]; 1043 } 1044 1045 return context; 1046 }; 1047 1048 //############################################################################## 1049 1050 /** 1051 * <p> 1052 * Tests if an object is an implementation of an {@link $interface}. This method only tests if the object has 1053 * already been marked as being an implementation of the interface; it does not actually analyze the object to 1054 * determine if all interface methods are implemented. See {@link $class.descriptor#$implements} and 1055 * {@link $interface#applyInterface} for performing the work of marking an object as implementing an interface. 1056 * </p> 1057 * 1058 * @param {mixed} obj any object or primitive. 1059 * @param {$interface} iface any interface. 1060 * @return {Boolean} true if the object implements the interface. 1061 */ 1062 $class.implementationOf = function(obj, iface) { 1063 if (obj._$class_interfaces && $class_library._hasOwnProperty(obj._$class_interfaces, iface.getName())) { 1064 return true; 1065 } 1066 1067 return $class.getClass(obj).implementsInterface(iface); 1068 }; 1069 1070 //############################################################################## 1071 1072 /** 1073 * <p> 1074 * Tests if an object is an instance of a class or an implementation of an interface. 1075 * </p> 1076 * 1077 * <p> 1078 * This method has special handling of primitive JavaScript values to consider them to be instances of their 1079 * corresponding classes. For example, <code class="brush:js">var x = 5; $class.instanceOf(x, Number)</code> 1080 * will return true, but <code class="brush:js">var x = 5; x instanceof Number</code> will return false. 1081 * </p> 1082 * 1083 * <p> 1084 * The $class library supplies dummy classes {@link Null} and {@link Undefined}. This method will identify 1085 * <em>null</em> and <em>undefined</em> as "instances" of those classes (respectively). 1086 * </p> 1087 * 1088 * <p> 1089 * If you do not need the special handling of primitive values, and the <em>type</em> is never going to be an 1090 * interface, then you can simply use the JavaScript <em>instanceof</em> operator for any classes created by 1091 * {@link $class}. 1092 * </p> 1093 * 1094 * @param {mixed} obj any object or primitive. 1095 * @param {Function|$interface} type A constructor function of some class, or an interface. 1096 * @return {Boolean} true if the object is an instance of the specified class (or implementation of the specified 1097 * interface). 1098 */ 1099 $class.instanceOf = function(obj, type) { 1100 if (type instanceof $interface) { 1101 return $class.implementationOf(obj, type); 1102 } 1103 1104 switch (typeof obj) { 1105 case "object": 1106 return (obj instanceof type) || 1107 // special case for null 1108 (obj === null && type == Null); 1109 1110 case "number": 1111 return (type == Number); 1112 1113 case "string": 1114 return (type == String); 1115 1116 case "boolean": 1117 return (type == Boolean); 1118 1119 case "function": 1120 default: 1121 return (obj instanceof type); 1122 1123 case "undefined": 1124 return (type == Undefined); 1125 } 1126 }; 1127 1128 //############################################################################## 1129 1130 /** 1131 * <p> 1132 * Gets the name of the class of the specified object. If the object is not an instance of a class that was created 1133 * by {@link $class} or adapted with {@link $class#adapt}, then it will be identified generically as "Object". 1134 * </p> 1135 * 1136 * <p> 1137 * JavaScript primitive values will be reported as being instances of their corresponding classes. 1138 * </p> 1139 * 1140 * <p> 1141 * The $class library supplies dummy classes {@link Null} and {@link Undefined}. This method will identify 1142 * <em>null</em> and <em>undefined</em> as "Null" and "Undefined" (respectively). 1143 * </p> 1144 * 1145 * <p> 1146 * <h2>Example:</h2> 1147 <pre class="brush:js"> 1148 $namespace("nested.namespace"); 1149 1150 $class("nested.namespace.Example", {}); 1151 1152 var example = new Example(); 1153 1154 alert($class.typeOf(example)) // alerts "nested.namespace.Example" 1155 alert($class.typeOf(5)) // alerts "5" 1156 alert($class.typeOf(null)) // alerts "Null" 1157 </pre> 1158 * </p> 1159 * 1160 * @param {mixed} obj any object or primitive. 1161 * @return {String} the name of the class of the object. 1162 */ 1163 $class.typeOf = function(obj) { 1164 return $class.getClass(obj).getName(); 1165 }; 1166 1167 //############################################################################## 1168 1169 /** 1170 * <p> 1171 * Gets the {@link $class} object that represents the class of the specified object. If the object is not an 1172 * instance of a class that was created by {@link $class} or adapted with {@link $class#adapt}, then Object's 1173 * $class object will be returned. 1174 * </p> 1175 * 1176 * <p> 1177 * For JavaScript primitive valuesthe $class objects of of their corresponding classes will be returned. 1178 * </p> 1179 * 1180 * <p> 1181 * For <em>null</em> and <em>undefined</em>, the $class objects for the dummy classes {@link Null} and 1182 * {@link Undefined} will be returned (respectively). 1183 * </p> 1184 * 1185 * @param {mixed} obj any object or primitive. 1186 * @return {$class} The object's class's $class object. 1187 */ 1188 $class.getClass = function(obj) { 1189 if (obj == null) { 1190 if (obj === undefined) { 1191 return Undefined.$class; 1192 } 1193 1194 return Null.$class; 1195 } 1196 1197 return obj._$class || Object.$class; 1198 }; 1199 1200 //############################################################################## 1201 1202 /** 1203 * <p> 1204 * Creates a new instance of a specified class using an array of arguments. 1205 * </p> 1206 * 1207 * <p> 1208 * For "native" JavaScript classes (those defined by the ECMAScript spec, such as Number, RegExp, etc), the 1209 * result of calling the constructor as a function will be returned. For classes that have corresponding primitive 1210 * types (Number, Boolean, String), this means that a primitive value will be returned instead of an instance of 1211 * the class. This is usually preferred for reasons such as instances of Strings, Boolean and Number always 1212 * evaluating to a boolean value of <em>true</em> even if their values are false-like. 1213 * </p> 1214 * 1215 * <p> 1216 * <h2>Example:</h2> 1217 <pre class="brush:js"> 1218 $class("Person", { 1219 $constructor: function(firstName, lastName) { 1220 this._firstName = firstName; 1221 this._lastName) = lastName); 1222 }, 1223 1224 getFullName: function() { 1225 return this._firstName + " " + this._lastName; 1226 } 1227 }); 1228 1229 var person = $class.instantiate(Person, ["John", "Doe"]); 1230 alert(person.getFullName()) // alerts "John Doe" 1231 1232 var number = $class.instantiate(Number, ["123"]); 1233 alert(number) // alerts "123" 1234 alert(typeof number) // alerts "number" 1235 alert(number instanceof Number) // alerts "false" 1236 </pre> 1237 * </p> 1238 * 1239 * @param {Function} ctor The constructor function for some class. 1240 * @param {Array} args An array of argument values to be passed to the constructor. 1241 * @return {mixed} A new instance of the specified class (or a primitive value). 1242 */ 1243 $class.instantiate = function(ctor, args) { 1244 if (ctor.$class && ctor.$class.isNative()) { 1245 return ctor.apply($class_library._globalObject, args); 1246 } else { 1247 $class_library._surrogateCtor.prototype = ctor.prototype; 1248 var result = new $class_library._surrogateCtor(); 1249 ctor.apply(result, args); 1250 1251 return result; 1252 } 1253 }; 1254 1255 //############################################################################## 1256 1257 /** 1258 * <p> 1259 * Creates an interface with the specified name and properties. Note that this documentation describes the behavior 1260 * of $interface when used as a method (without the "new" keyword). This should only be called as a method rather 1261 * than as a constructor. Instances of $interface are created as a private implementation detail of $interface being 1262 * called as a method. 1263 * </p> 1264 * 1265 * <p> 1266 * An interface describes methods that must be implemented by a class that claims to implement the interface. 1267 * Interfaces can also extend other interfaces and contain static properties/methods. 1268 * See {@link $interface.descriptor} for details. 1269 * </p> 1270 * 1271 * @class This class encapsulates information about an interface, but its constructor is used as a function 1272 * (without the "new" keyword) to create an interface. It is used as a constructor for private implementation 1273 * purposes only. 1274 * 1275 * @constructor 1276 * 1277 * @param {String} name The name of the interface. The name may describe a path of multiple nested 1278 * namespace/class/object names separated by a period (.). The entire path to the interface name is expected 1279 * to exist already. See {@link $namespace} for details about creating namespaces. 1280 * 1281 * @param {$interface.descriptor} descriptor An object containing the properties of the interface, which can 1282 * describe methods, static methods, static properties, and more. 1283 * 1284 * @return {$interface} The newly created interface. 1285 */ 1286 function $interface(name, descriptor) { 1287 // if being called as a constructor... 1288 if (this instanceof $interface) { 1289 if (!descriptor.calledFrom$interface) { 1290 $class_library._error("Error in attempt to define interface [" + name + "]: " 1291 + "Do not directly instantiate $interface! Call $interface as a function instead."); 1292 } 1293 1294 this._name = name; 1295 this._methods = {}; 1296 this._methodsArray = null; 1297 this._interfaces = {}; 1298 this._interfaces[name] = this; 1299 return; 1300 } 1301 1302 var iface = new $interface(name, {calledFrom$interface: true}); 1303 1304 // extend interfaces 1305 if ($class_library._hasOwnProperty(descriptor, "$extends")) { 1306 var ifaces = descriptor.$extends; 1307 1308 // convert to an array if it is a single object 1309 if (!(ifaces instanceof Array)) { 1310 ifaces = [ifaces]; 1311 } 1312 1313 for (var i = 0, ifacesLength = ifaces.length; i < ifacesLength; ++i) { 1314 // make sure the 'interface' to extend is really an interface 1315 if (!(ifaces[i] instanceof $interface)) { 1316 if (ifaces[i] == null) { 1317 $interface._propertyError(name, "$extends[" + i +"]", "Cannot extend null. You probably forgot to include the JS file containing the declaration of the parent class."); 1318 } else { 1319 $interface._propertyError(name, "$extends[" + i +"]", "$interface or array of $interfaces expected"); 1320 } 1321 } 1322 1323 iface._extend(ifaces[i]); 1324 } 1325 } 1326 1327 var specialProperties = {$extends:true}; 1328 var invalidProperties = {$constructor:true,$implements:true,$static:true}; 1329 1330 // process all properties in the descriptor 1331 for (var propertyName in descriptor) { 1332 // skip over the special properties 1333 if ($class_library._hasOwnProperty(specialProperties, propertyName)) { 1334 continue; 1335 } 1336 1337 // check for invalid properties 1338 if ($class_library._hasOwnProperty(invalidProperties, propertyName)) { 1339 $interface._propertyError(name, propertyName, "invalid property for an $interface"); 1340 } 1341 1342 var value = descriptor[propertyName]; 1343 1344 if (value instanceof $class._ModifiedProperty && value.getModifier() == "static") { 1345 iface._addStatic(propertyName, value.getValue()); 1346 } else if (value instanceof Function) { 1347 iface._addMethod(propertyName); 1348 } else { 1349 $interface._propertyError(name, propertyName, "Function or static value expected"); 1350 } 1351 } 1352 1353 // store the interface so it can be referenced by the specified name 1354 try { 1355 return eval(name + " = iface;"); 1356 } catch (error) { 1357 $interface._error(name, "Invalid interface name: " + error.message); 1358 } 1359 } 1360 1361 //############################################################################## 1362 1363 $interface.prototype = { 1364 //############################################################################ 1365 1366 /** 1367 * Gets the name of the interface. 1368 * @return {String} the full name of the interface. 1369 */ 1370 getName: function() { 1371 return this._name; 1372 }, 1373 1374 //############################################################################ 1375 1376 /** 1377 * Tests if the interface (or any inherited interfaces) contains a method. 1378 * @param {String} methodName the name of a potential method. 1379 * @return {Boolean} true if this interface has a method with the specified name. 1380 */ 1381 hasMethod: function(methodName) { 1382 return Boolean(this._methods[methodName]); 1383 }, 1384 1385 //############################################################################ 1386 1387 /** 1388 * Gets a list of all methods in this interface (including methods of inherited interfaces). 1389 * @return {String[]} an array of method names. 1390 */ 1391 getMethods: function() { 1392 if (!this._methodsArray) { 1393 this._methodsArray = []; 1394 1395 for (var methodName in this._methods) { 1396 this._methodsArray.push(methodName); 1397 } 1398 } 1399 1400 return this._methodsArray; 1401 }, 1402 1403 //############################################################################ 1404 1405 /** 1406 * <p> 1407 * Applies this interface to the specified object or class. This marks the object or class as implementing this 1408 * interface. The debug version of the $class library will verify that all interface methods are actually 1409 * implemented by the object or class. 1410 * </p> 1411 * 1412 * <p> 1413 * This method has two intended uses: 1414 * <ol> 1415 * <li> 1416 * Retroactively mark a class as implementing an interface. This allows you to avoid modifying the definition 1417 * of a class. You can pass either the constructor of the class, or the class's $class object 1418 * to applyInterface. Interfaces can be applied to classes that are not within the $class framework by 1419 * applying the interface to the class's constructor, but requires that all interface methods are implemented 1420 * on the class's prototype rather than assigned directly to properties in the constructor. The interface 1421 * information will be preserved if the class is later adapted into the $class framework via 1422 * {@link $class.adapt}. 1423 * </li> 1424 * <li> 1425 * Mark a specific object as implementing an interface. This could be an anonymous object, which would be 1426 * like creating an anonymous implementation of the interface. IMPORTANT: Interfaces cannot be applied to 1427 * directly to functions. Doing so will be interpreted as an attempt to apply the interface to a class that 1428 * is not within the $class framework (see above). 1429 * </li> 1430 * </ol> 1431 * </p> 1432 * 1433 * <p> 1434 * When used to apply an interface to an object (not a class), be aware that a property (_$class_interfaces) will 1435 * be added to the object. If the object cannot be polluted with an extra property, then use {@link #wrapObject}. 1436 * </p> 1437 * 1438 * <p> 1439 * <h2>Debug version errors:</h2> 1440 * <ul> 1441 * <li>Interface method not implemented in the class or object. The error message will list all methods not 1442 * implemented.</li> 1443 * </ul> 1444 * </p> 1445 * 1446 * @param {$class|Function|Object} obj The constructor or $class object of a class 1447 * that implements all methods of this interface (to apply the interface to a class), or any object that 1448 * implements all methods of this interface to apply the interface to that particular object. 1449 * @return {$class|Function|Object} The <em>obj</em> argument. 1450 */ 1451 applyInterface: function(obj) { 1452 var classInfo = obj instanceof $class ? obj : obj.$class; 1453 1454 if (classInfo) { 1455 obj = classInfo.getPrototype(); 1456 } else if (obj.constructor == Function) { 1457 obj = obj.prototype; 1458 } 1459 1460 var missingInterfaceMethods = new Array(); 1461 1462 for (var methodName in this._methods) { 1463 if (!(obj[methodName] instanceof Function)) { 1464 var ifaceName = this._methods[methodName]; 1465 missingInterfaceMethods.push(ifaceName + "." + methodName); 1466 } 1467 } 1468 1469 if (missingInterfaceMethods.length > 0) { 1470 var message = "Error in " + this.getName() + ".applyInterface(" + (classInfo ? classInfo.getName() : "[object]") + "); The following interface methods must be implemented: "; 1471 1472 for (var i = 0; i < missingInterfaceMethods.length; ++i) { 1473 message += "[" + missingInterfaceMethods[i] + "] "; 1474 } 1475 1476 $class_library._error(name, message); 1477 } 1478 1479 if (classInfo) { 1480 this._applyInterface([classInfo]); 1481 } else { 1482 if (!obj._$class_interfaces) { 1483 obj._$class_interfaces = {}; 1484 } 1485 1486 for (var ifaceName in this._interfaces) { 1487 obj._$class_interfaces[ifaceName] = this._interfaces[ifaceName]; 1488 } 1489 } 1490 1491 return obj; 1492 }, 1493 1494 //############################################################################ 1495 1496 _applyInterface: function(classes) { 1497 for (var i = 0, classInfo; classInfo = classes[i]; ++i) { 1498 for (var ifaceName in this._interfaces) { 1499 classInfo._interfaces[ifaceName] = this._interfaces[ifaceName]; 1500 } 1501 1502 this._applyInterface(classInfo._children); 1503 } 1504 }, 1505 1506 //############################################################################ 1507 1508 /** 1509 * <p> 1510 * Creates an anonymous implementation of this interface whose methods are all implemented by simply calling the 1511 * method of the name on the specified object. This may be an alternative to applying the interface directly to 1512 * the object, which will add a property to the object that may be undesirable 9see {@link #applyInterface}. The 1513 * debug version of the $class library will verify that the object implements all methods of this interface. 1514 * </p> 1515 * 1516 * @param {Object} obj An object that implements all methods of this interface. 1517 * @return {Object} a new object that implements this interface by calling methods on the <em>obj</em> argument. 1518 */ 1519 wrapObject: function(obj) { 1520 var result = {_obj: obj}; 1521 1522 result.toString = new Function("return '[" + $class.typeOf(obj) + " wrapped by " + this.getName() + "]';"); 1523 1524 for (var methodName in this._methods) { 1525 // Only create the interface method if a corresponding method exists on the object. This allows applyInterface to fail. 1526 if (typeof obj[methodName] == "function") { 1527 result[methodName] = new Function("return this._obj['" + methodName + "'].apply(this._obj, arguments);"); 1528 } 1529 } 1530 1531 return this.applyInterface(result); 1532 }, 1533 1534 //############################################################################ 1535 1536 /** 1537 * Overridden to return a string that identifies this $interface. 1538 * @return {String} a string in the form "[$interface _name_]", where _name_ is the full name of this interface. 1539 */ 1540 toString: function() { 1541 return "[$interface " + this._name + "]"; 1542 }, 1543 1544 //############################################################################ 1545 1546 _extend: function(iface) { 1547 $class_library._copyObject(this._methods, iface._methods); 1548 $class_library._copyObject(this._interfaces, iface._interfaces); 1549 }, 1550 1551 //############################################################################ 1552 1553 _addStatic: function(propertyName, value) { 1554 this[propertyName] = value; 1555 }, 1556 1557 //############################################################################ 1558 1559 _addMethod: function(propertyName) { 1560 this._methods[propertyName] = this._name; 1561 } 1562 1563 //############################################################################ 1564 }; 1565 1566 //############################################################################## 1567 1568 $interface._error = function(name, message) { 1569 $class_library._error("Error in $interface(\"" + name + "\"" + ", ...): " + message); 1570 }; 1571 1572 //############################################################################## 1573 1574 $interface._propertyError = function(name, propertyName, message) { 1575 $class_library._error("Error in $interface(\"" + name + "\"" + ", ...), property [" + propertyName + "]: " + message); 1576 }; 1577 1578 //############################################################################## 1579 1580 /** 1581 * <p> 1582 * Creates an abstract method in a class. This is a property modifier for use within a 1583 * {@link $class.descriptor}. 1584 * </p> 1585 * <p> 1586 * An abstract method has no implementation and prevents the class from being instantiated. A class with 1587 * any abstract methods is inherently abstract; the class itself does not need to be (and cannot be) marked 1588 * as absract. Any class that extends an abstract class is also abstract, unless it implements (overrides) all 1589 * inherited abstract methods. The abstract modifier may only be applied to non-{@link $final} and 1590 * non-{@link $static} methods. 1591 * </p> 1592 * 1593 * <p> 1594 * <h2>Debug version errors:</h2> 1595 * <ul> 1596 * <li>Attempt to instantiate an abstract class. The error message will indicate the abstract class and will 1597 list all abstract methods and from which class they were inherited.</li> 1598 * <li>Attempt to create an abstract property (not a method). The error message will indicate the class and 1599 * property name.</li> 1600 * </ul> 1601 * </p> 1602 * 1603 * <p> 1604 * <h2>Example:</h2> 1605 <pre class="brush:js"> 1606 // abstract class that implements some algorithm, 1607 // but leaves some details unspecified 1608 $class("AbstractBase", { 1609 // returns an array of numbers 1610 getData: $abstract(function() {}), 1611 1612 // "protected" implementation detail of some algorithm. 1613 _shouldAddValue: $abstract(function(value) {}), 1614 1615 // the algorithm that uses the abstract methods 1616 performSomeAlgorithmWithData: $final(function() { 1617 var data = this.getData(); 1618 var result = 0; 1619 1620 for (var i = 0; i < data.length; ++i) { 1621 if (this._shouldAddValue(data[i]) { 1622 result += data[i]; 1623 } 1624 } 1625 1626 return result; 1627 }) 1628 }); 1629 1630 // class that implements the abstract methods 1631 $class("Implementation", { 1632 $extends: AbstractBase, 1633 1634 $constructor: function(data) { 1635 this._data = data; 1636 }, 1637 1638 getData: function() { 1639 return this._data; 1640 }, 1641 1642 _shouldAddValue: function(value) { 1643 return value > 0; 1644 } 1645 }); 1646 1647 var abstractBase = new AbstractBase(); // ERROR! 1648 1649 var impl = new Implementation([1, -5, 9, -10, 5]); 1650 alert(impl.performSomeAlgorithmWithData()); // alerts "15" 1651 </pre> 1652 * </p> 1653 * 1654 * @param {Function} method A function. The function itself is not actually used, but it must be provided. 1655 * This both allows for a sanity check that you aren not trying to create an abstract property (can't be done) 1656 * and provides a convenient place for you to declare/document the arguments. 1657 * 1658 * @return {Object} Something magical that, when assigned to a property of a {@link $class.descriptor}, causes 1659 * an abstract method to be created. 1660 */ 1661 function $abstract(method) { 1662 return new $class._ModifiedProperty("abstract", method); 1663 } 1664 1665 /** 1666 * <p> 1667 * Creates a static property or method in a class or interface. This is a property modifier for use within a 1668 * {@link $class.descriptor} or {@link $interface.descriptor}. 1669 * </p> 1670 * <p> 1671 * A static property/method is accessible as a property of the class or interface without having an instance. 1672 * Within static methods, the "this" keyword will refer to the class or interface to which the static method 1673 * belongs. Static methods/properties are not inherited; you must access them on the exact class or interface 1674 * in which they were defined. Note that static methods by definition cannot be made {@link $abstract} or 1675 * {@link $final}. 1676 * </p> 1677 * 1678 * <p> 1679 * <h2>Example:</h2> 1680 <pre class="brush:js"> 1681 $class("Sample", { 1682 $constructor: function(name, type) { 1683 this._name = name; 1684 this._type = type; 1685 }, 1686 1687 equalTo: function(other) { 1688 return other 1689 && other instanceof Sample 1690 && other._name == this._name 1691 && other._type == this._type; 1692 }, 1693 1694 // static constants for indicating the "type" 1695 Type: $static({ 1696 GOOD: 1, 1697 BETTER: 2, 1698 BEST: 3 1699 }), 1700 1701 // static factory method 1702 createBestSample: $static(function(name) { 1703 // "this" in static methods refers to the class/constructor 1704 return new this(name, this.Type.BEST); 1705 }) 1706 }); 1707 1708 var sample1 = new Sample("Bob", Sample.Type.BEST); 1709 var sample2 = Sample.createBestSample("Bob"); 1710 1711 alert(sample1.equalTo(sample2)); // alerts "true" 1712 </pre> 1713 * </p> 1714 * 1715 * @param {mixed} value Any value. May be a function. 1716 * 1717 * @return {Object} Something magical that, when assigned to a property of a {@link $class.descriptor} or 1718 * {@link $interface.descriptor}, causes a static property to be created. 1719 */ 1720 function $static(value) { 1721 return new $class._ModifiedProperty("static", value); 1722 } 1723 1724 /** 1725 * <p> 1726 * Creates a final method in a class, or creates a final class. This is a property modifier for use within a 1727 * {@link $class.descriptor}. 1728 * </p> 1729 * <p> 1730 * A final method is not allowed to be overridden by descendent classes. The final modifier can also be applied 1731 * to the {@link $class.descriptor#$constructor} of a class to prevent the class from being overridden 1732 * althogether. The final modifier may only be applied to non-{@link $abstract} and non-{@link $static} methods. 1733 * </p> 1734 * <p> 1735 * 1736 * <h2>Debug version errors:</h2> 1737 * <ul> 1738 * <li>Attempt to extend a class whose constructor is final.</li> 1739 * <li>Attempt to override an inherited final method.</li> 1740 * </ul> 1741 * </p> 1742 * 1743 * <p> 1744 * <h2>Example:</h2> 1745 <pre class="brush:js"> 1746 // extend this class to inherit a unique ID 1747 $class("UniquelyIdentifiable", { 1748 $constructor: function() { 1749 this._id = "UniquelyIdentifiable_" + (UniquelyIdentifiable._idCounter++); 1750 }, 1751 1752 // If this method were ever overridden to do something different, it could 1753 // break the contract of this class (promises a unique ID). Making it final 1754 // keeps it completely within control of this class. 1755 getId: $final(function() { 1756 return this._id; 1757 }), 1758 1759 _idCounter: $static(0) 1760 }); 1761 </pre> 1762 * </p> 1763 * 1764 * @function 1765 * 1766 * @param {Function} method The implementation of the final method. 1767 * 1768 * @return {Object} Something magical that, when assigned to a property of a {@link $class.descriptor), causes a final 1769 * method to be created. 1770 */ 1771 function $final(method) { 1772 return new $class._ModifiedProperty("final", method); 1773 } 1774 1775 //############################################################################## 1776 1777 (function() { 1778 var nativeClassNames = [ 1779 "Object", 1780 "Array", 1781 "String", 1782 "Number", 1783 "Boolean", 1784 "Function", 1785 "RegExp", 1786 "Date", 1787 1788 "Error", 1789 "EvalError", 1790 "RangeError", 1791 "ReferenceError", 1792 "SyntaxError", 1793 "TypeError", 1794 "URIError" 1795 ]; 1796 1797 for (var i = 0, className; className = nativeClassNames[i]; ++i) { 1798 $class.adapt(this[className], className, true); 1799 } 1800 })(); 1801 1802 $class.adapt($namespace, "$namespace"); 1803 $class.adapt($class, "$class"); 1804 $class.adapt($interface, "$interface"); 1805 1806 //############################################################################## 1807 1808 /** 1809 * @name Undefined 1810 * @class 1811 * A dummy class used by various $class libray utility methods to represent the type of <em>undefined</em> 1812 * @see $class.typeOf 1813 * @see $class.instanceOf 1814 * @see $class.getClass 1815 */ 1816 $class("Undefined", { 1817 Undefined: $final(function() { 1818 $class_library._error("Attempted instantiation of the Undefined class."); 1819 }) 1820 }); 1821 1822 /** 1823 * @name Null 1824 * @class 1825 * A dummy class used by various $class libray utility methods to represent the type of <em>null</em> 1826 * @see $class.typeOf 1827 * @see $class.instanceOf 1828 * @see $class.getClass 1829 */ 1830 $class("Null", { 1831 Null: $final(function() { 1832 $class_library._error("Attempted instantiation of the Null class."); 1833 }) 1834 }); 1835 1836 //############################################################################## 1837 1838 /** 1839 * @name $class.descriptor 1840 * @class 1841 * <p> 1842 * This is a fake class used to document the descriptor object that is passed to the {@link $class} 1843 * function to define a class. The documented properties are handled specially as described by each property. 1844 * All possible property names beginning with a dollar sign ($), or an underscore and dollar sign (_$) are to 1845 * be considered reserved for use by the $class library. Anything else is fair game for defining properties 1846 * and methods in a class. 1847 * </p> 1848 * 1849 * <p> 1850 * Unless documented otherwise, all properties in the descriptor are simply copied to the prototype of the 1851 * new class, making them instance properties or instance methods. It is recommended that you avoid creating 1852 * instance properties that are not methods. Instance properties of the class should generally be defined 1853 * and initialized in the constructor to avoid non-obvious bugs that can arise from multiple instances 1854 * unintentionally sharing a reference to an array or object through a prototype property. 1855 * </p> 1856 * 1857 * <p> 1858 * <h2>Property Modifiers</h2> 1859 * The following modifiers can be applied to properties in the descriptor. See the documentation for each 1860 * modifier for details and examples. 1861 * <ul> 1862 * <li>{@link $static} - create a static property or method.</li> 1863 * <li>{@link $final} - prevent a method from being overridden (can also be applied to the {@link #$constructor}).</li> 1864 * <li>{@link $abstract} - create an abstract method.</li> 1865 * </ul> 1866 * You may have noticed that there is no "private" modifier. I know that there's techniques for making 1867 * "private fields" that truly cannot be accessed externally, but I don't believe the benefit outweighs the cost 1868 * in overhead and reduced maintainability of the code (it's hard to recognize which identifiers are actually 1869 * private fields vs local or global variables). I prefer the simplicity of a leading underscore (_) for the names 1870 * of private fields and methods. 1871 * </p> 1872 * 1873 * <p> 1874 * <a name="$base"></a> 1875 * <h2>this.$base</h2> 1876 * In any non-static method (including the constructor), a special property named "$base" can be used to call 1877 * the overridden implementation of the same method. The overhead of supporting this special property 1878 * is only added if the method contains a reference to this.$base (as determined by a simple regular expression). 1879 <pre class="brush:js"> 1880 $class("Person", { 1881 $constructor: function(name) { 1882 this._name = name; 1883 }, 1884 1885 getName: function() { 1886 return this._name; 1887 } 1888 }); 1889 1890 $class("Officer", { 1891 $extends: Person, 1892 1893 $constructor: function(name, rank) { 1894 // call Person's constructor implementation 1895 this.$base(name); 1896 this._rank = rank; 1897 }, 1898 1899 getName: function() { 1900 // add the officer's rank to the name 1901 return this._rank + " " + this.$base(); 1902 } 1903 }); 1904 1905 var officer = new Officer("Coltraine", "Sheriff"); 1906 alert(officer.getName()); // alerts "Sheriff Coltraine" 1907 </pre> 1908 * </p> 1909 */ 1910 $class_library._$class_descriptor_docs = 1911 /** 1912 * @lends $class.descriptor.prototype 1913 */ 1914 { 1915 /** 1916 * <p> 1917 * A function that will be used to implement the constructor of the class. A constructor is not required. If 1918 * not specified, a default constructor will be created. Default constructors will automatically call the 1919 * parent class's constructor with all the arguments that were passed in. Use the special 1920 * <a href="#$base">this.$base</a> property to 1921 * call the parent class's constructor (a.k.a., initializing the parent class) with specific arguments. If a 1922 * constructor is provided, but there is no usage of this.$base, the parent class's constructor will be 1923 * automatically called before your constructor implementation (with all the same arguments). 1924 * </p> 1925 * 1926 * <p> 1927 * The constructor may be modified with the {@link $final} modifier to make the entire class final (prevents 1928 * other classes from extending this class). 1929 * </p> 1930 * 1931 * <p> 1932 * The constructor can also be provided as a property whose name is the same as the class's name. 1933 * </p> 1934 * 1935 * <p> 1936 * <h2>Debug version warnings:</h2> 1937 * <ul> 1938 * <li>No constructor specified; using default constructor.</li> 1939 * </ul> 1940 * </p> 1941 * 1942 * <p> 1943 * <h2>Debug version errors:</h2> 1944 * <ul> 1945 * <li>Constructor value is not a function.</li> 1946 * </ul> 1947 * </p> 1948 * 1949 * <p> 1950 * <h2>Optimized trivial constructors:</h2> 1951 * If there nothing in the body of the constructor function (no comments; only whitespace), or if no 1952 * constructor is provided, then it is considered to be a trivial constructor. In the non-debug version of 1953 * the $class library, trivial constructors are skipped when instantiating classes with a chain of 1954 * inheritance. For example, let's say there is an inheritance chain of classes: A <- B <- C <- D <- E 1955 * (A is the base class in this chain). If A, B and D all have trivial constructors, then the instantiation 1956 * of B will simply execute a single empty function. Instantiating C will execute only C's constructor. 1957 * Instantiating D will execute a wrapper function that simply call's C's constructor. Instantiating E will 1958 * execute E's constructor, then skip right to C's constructor (even if E's constructor explicitly calls 1959 * <a href="#$base">this.$base</a>). 1960 * </p> 1961 * 1962 * <p> 1963 * <h2>Example:</h2> 1964 <pre class="brush:js"> 1965 $namespace("Shapes"); 1966 1967 $class("Shapes.Rectangle", { 1968 // this is a constructor 1969 $constructor: function(width, height) { 1970 this._width = width; 1971 this._height = height; 1972 } 1973 }); 1974 1975 $class("Shapes.Square", { 1976 $extends: Rectangle, 1977 1978 // this is a constructor too 1979 Square: function(size) { 1980 // Call Square's constructor implementation 1981 this.$base(size, size); 1982 } 1983 }); 1984 </pre> 1985 * </p> 1986 * 1987 * @type Function 1988 */ 1989 $constructor: null, 1990 1991 /** 1992 * <p> 1993 * A class to be inherited by this class. Instances of this class will be considered to be instances of the 1994 * specified parent class by both the JavaScript "instanceof" keyword, and {@link $class#instanceOf}. All 1995 * instance methods and properties will be inherited. This class will be considered to implement all 1996 * {@link $interface}s that are implemented by the parent class. 1997 * </p> 1998 * 1999 * <p> 2000 * See {@link #$constructor} for details about initializing the parent class. 2001 * </p> 2002 * 2003 * <p> 2004 * <h2>Debug version errors:</h2> 2005 * <ul> 2006 * <li>$extends is specified, but the value is null/undefined. This usually indicates that you attempted 2007 * to extend a class and forgot to include the JS file that defines that class, or maybe you misspelled 2008 * the class name.</li> 2009 * <li>Attempt to extend something that is not a Function.</li> 2010 * <li>Attempt to extend a class whose {@link #$constructor} is {@link $final}.</li> 2011 * <li>Attempt of a non-multiton class to extend a multiton ({@link $class.descriptor#$multiton}).</li> 2012 * <li>Attempt of a non-singleton class to extend a singleton ({@link $class.descriptor#$singleton}).</li> 2013 * </ul> 2014 * </p> 2015 * 2016 * <p> 2017 * <h2>Debug version warnings:</h2> 2018 * <ul> 2019 * <li>The class being extended is being automatically adapted into the $class framework with a generated 2020 * name (see {@link $class.adapt})</li> 2021 * </ul> 2022 * </p> 2023 * 2024 * <p> 2025 * <h2>Example:</h2> 2026 <pre class="brush:js"> 2027 $class("Rectangle", { 2028 // this is a constructor 2029 $constructor: function(width, height) { 2030 this._width = width; 2031 this._height = height; 2032 }, 2033 2034 getArea: function() { 2035 return this._width * this.height; 2036 } 2037 }); 2038 2039 $class("Square", { 2040 $extends: Rectangle, 2041 2042 // this is a constructor too 2043 Square: function(size) { 2044 // Call Square's constructor implementation 2045 this.$base(size, size); 2046 } 2047 }); 2048 2049 var square = new Square(10); 2050 2051 alert(square instanceof Square); // alerts "true" 2052 alert(square instanceof Rectangle); // alerts "true" 2053 alert(square.getArea()); // alerts "100" 2054 </pre> 2055 * </p> 2056 * 2057 * @type Function 2058 * 2059 * @see $class#instanceOf 2060 */ 2061 $extends: null, 2062 2063 /** 2064 * <p> 2065 * One or more {@link $interface}s that are implemented by this class. All methods of all interfaces must be 2066 * present in this class. Inherited methods and {@link $abstract} methods both satisfy this requirement. 2067 * </p> 2068 * 2069 * <p> 2070 * <h2>Debug version errors:</h2> 2071 * <ul> 2072 * <li>Interface method not implemented in the class. The error message will list all methods not 2073 * implemented.</li> 2074 * </ul> 2075 * </p> 2076 * 2077 * <p> 2078 * <h2>Example:</h2> 2079 <pre class="brush:js"> 2080 $interface("Identifiable", { 2081 getId: function() {} 2082 }); 2083 2084 $interface("Named", { 2085 getName: function() {} 2086 }); 2087 2088 $class("Person", { 2089 $implements: [Identifiable, Named], 2090 2091 $constructor: function(governmentId, name) { 2092 this._governmentId = governmentId; 2093 this._name = name; 2094 }, 2095 2096 getId: function() { 2097 return this._governemtnId; 2098 }, 2099 2100 getName: function() { 2101 return this._name; 2102 } 2103 }); 2104 </pre> 2105 * </p> 2106 * 2107 * @type ($interface|$interface[]) 2108 * 2109 * @see $class#implementsInterface 2110 * @see $class#implementionOf 2111 * @see $class#instanceOf 2112 */ 2113 $implements: null, 2114 2115 /** 2116 * <p> 2117 * A static initializer function that is executed immediately after the class is created. This can be used 2118 * to perform complex initialization of {@link $static} properties. Like static methods, the static 2119 * initializer is executed within the context of the class/constructor. 2120 * </p> 2121 * 2122 * <p> 2123 * <h2>Example:</h2> 2124 <pre class="brush:js"> 2125 $class("Example", { 2126 ValuesMap: $static({ 2127 ZERO: 0, 2128 ONE: 1, 2129 TWO: 2, 2130 THREE: 3 2131 }), 2132 2133 // initialized in the static initializer 2134 ValuesReverseMap: $static({}), 2135 2136 $static: function() { 2137 for (var i in this.ValuesMap) { 2138 this.ValuesReverseMap[this.ValuesMap[i]] = i; 2139 } 2140 } 2141 }); 2142 2143 // static initializer has already been executed by this point. 2144 // alerts "true" 2145 alert(Example.ValuesReverseMap[Example.ValuesMap["TWO"]] == "TWO"); 2146 </pre> 2147 * </p> 2148 * 2149 * @type Function 2150 */ 2151 $static: null, 2152 2153 /** 2154 * <p> 2155 * By default, every class created by {@link $class} will have its own toString implementation that returns a 2156 * string in the form "[object _name_]", where _name_ is the full name of the class. This can be useful for 2157 * debugging. toString can be overridden with a custom implementation in any class. 2158 * </p> 2159 * 2160 * @type Function 2161 */ 2162 toString: null, 2163 2164 /** 2165 * <p> 2166 * Configures a class as a singleton. A singleton cannot be directly instantiated. A static "getInstance" method 2167 * is automatically created that will take care of instantiating the class the first time and returning the same 2168 * instance from all subsequent calls. 2169 * </p> 2170 * 2171 * <p> 2172 * The value of this property can either be an anonymous {@link $class.descriptor.singleton_config} object, or 2173 * simply a boolean "true" if the defaults of the config object are acceptable. 2174 * </p> 2175 * 2176 * <p> 2177 * The advantages of creating a singleton this way (as opposed to simply creating a global named object with some 2178 * methods and properties) is that it defers initialization until the first use in code, and it can extend another 2179 * class, implement interfaces, etc. 2180 * </p> 2181 * 2182 * <p> 2183 * A singleton can extend any non-{@link $final} class that is not a multiton ({@link $class.descriptor#$multiton}). 2184 * Yes, a singleton can extend another singleton. Instances are not shared between parent and child singleton, so by 2185 * extending singleton, you will technically end up with two equivalent instances of the parent class (one of 2186 * them actually being an instance of the child class). Non-singleton classes cannot extend a singleton. 2187 * What does it mean for a singleton to extend another singleton? I dunno; you tell me. I couldn't absolutely 2188 * convince myself to disallow it, so here it is. 2189 * </p> 2190 * 2191 * <p> 2192 * <h2>Warning:</h2> 2193 * If you currently only need one instance and you're just being lazy and designing your class as a singleton so 2194 * that you can call getInstance() in multiple places instead of passing a reference around, you should really 2195 * just spend the little bit of extra time to instantiate your class in one place and pass it to the constructors 2196 * of the various classes that need to reference it. You'll thank me some day when you need to reuse your code 2197 * some day in a way such that your singleton needs to be dynamically reconfigured or you actually find that you 2198 * really do need multiple simultaneous instances with different configurations. It's much easier, cleaner and 2199 * less error-prone to reinstantiate objects with new data than it is to hack up a singleton to reconfigure 2200 * itself. 2201 * </p> 2202 * 2203 * <p> 2204 * <h2>Example:</h2> 2205 <pre class="brush:js"> 2206 $class("Example", { 2207 $singleton: true, 2208 2209 $constructor: function() { 2210 this._value = 0; 2211 }, 2212 2213 getValue: function() { 2214 return this._value; 2215 }, 2216 2217 incrementValue: function() { 2218 ++this._value; 2219 } 2220 }); 2221 2222 var example1 = Example.getInstance(); 2223 var example2 = Example.getInstance(); 2224 2225 alert(example1 == example2); // alerts "true" 2226 alert(example1.getValue()); // alerts "0" 2227 alert(example2.getValue()); // alerts "0" 2228 2229 example1.incrementValue(); 2230 alert(example2.getValue()); // alerts "1" 2231 </pre> 2232 * </p> 2233 * 2234 * @type ($class.descriptor.singleton_config|Boolean) 2235 */ 2236 $singleton: null, 2237 2238 /** 2239 * <p> 2240 * Configures a class as a multiton. A multiton cannot be directly instantiated. A static "getInstance" method 2241 * is automatically created that will take care of instantiating the class the first time with a given set of and 2242 * arguments and returning the same instance from all subsequent calls with the same set of arguments. 2243 * </p> 2244 * 2245 * <p> 2246 * The value of this property can either be an anonymous {@link $class.descriptor.multiton_config} object, or 2247 * simply a boolean "true" if the defaults of the config object are acceptable. 2248 * </p> 2249 * 2250 * <p> 2251 * Multitons are generally good for classes that are expensive to instantiate and if only a relatively small 2252 * number of instances will each be referenced multiple times, but you want to avoid the hassle of maintaining 2253 * references to instances. This is a very simple caching system that can provide performance improvements, but 2254 * does not allow for any control over the cache, such as enforcing a limit to the cache. 2255 * </p> 2256 * 2257 * <p> 2258 * A multiton can extend any non-{@link $final} class that is not a singleton ({@link $class.descriptor#$singleton}). 2259 * Yes, a multiton can extend another multiton. Instances are not shared between parent and child multitons, so by 2260 * extending multitons, you can technically end up with two equivalent instances of the parent class (one of 2261 * them actually being an instance of the child class). Non-multiton classes cannot extend a multiton. 2262 * What does it mean for a multiton to extend another multiton? I dunno; you tell me. I couldn't absolutely 2263 * convince myself to disallow it, so here it is. 2264 * </p> 2265 * 2266 * <p> 2267 * <h2>Warning:</h2> 2268 * By default, the cache key is built very naively. If there is any way that different parameter values could 2269 * produce identical instances of your class, then you must provide your own cache key algorithm. This problem 2270 * is illustrated in the example. See {@link $class.descriptor.multiton_config#createCacheKey} for details about 2271 * providing your own cache key algorithm. 2272 * </p> 2273 * 2274 * <p> 2275 * <h2>Example:</h2> 2276 <pre class="brush:js"> 2277 $class("Example", { 2278 $multiton: true, 2279 2280 // name defaults to "John Doe" if empty or null 2281 $constructor: function(name) { 2282 this._name = name || "John Doe"; 2283 }, 2284 2285 getName() { 2286 return this._name; 2287 } 2288 }); 2289 2290 var example1 = Example.getInstance("John Doe"); 2291 var example2 = Example.getInstance("John Doe"); 2292 2293 // same instance if instantiated with the same arguments 2294 alert(example1 == example2) // alerts "true" 2295 2296 // instantiate with different arguments... 2297 var example3 = Example.getInstance(null); 2298 2299 // different instance 2300 alert(example2 == example3) // alerts "false" 2301 // ... but the same name 2302 alert(example2.getName() == example3.getName()) // alerts "false" 2303 </pre> 2304 * </p> 2305 * 2306 * @type ($class.descriptor.multiton_config|Boolean) 2307 */ 2308 $multiton: null 2309 }; 2310 2311 /** 2312 * @name $class.descriptor.singleton_config 2313 * @class 2314 * <p> 2315 * This is a fake class to document the configuration object for use with {@link $class.descriptor#$singleton} 2316 * to setup a class as a singleton. If the defaults are acceptable, you can simply provide a value of 2317 * <em>true</em> instead of a singleton config object. 2318 * </p> 2319 */ 2320 $class_library._$singleton_config_docs = 2321 /** 2322 * @lends $class.descriptor.singleton_config.prototype 2323 */ 2324 { 2325 /** 2326 * <p> 2327 * A function that is used to create the singleton instance. This function receives no arguments and is 2328 * expected to return an instance of the class. The function is called within the context of the 2329 * class/constructor. Defaults to a function that simply instantiates the class with no constructor arguments. 2330 * </p> 2331 * 2332 * <p> 2333 * This property is useful for when you want to have a singleton, but it needs be instantiated with 2334 * "dynamic static data"; data that is determined by the server so that it cannot be hard-coded in the JavaScript, 2335 * but the data never changes during the lifetime of the script, so it is static as far as the script is 2336 * concerned. 2337 * </p> 2338 * 2339 * <p> 2340 * Defaults to a function that simply instantiates the class with the same arguments. 2341 * </p> 2342 * 2343 * <p> 2344 * <h2>Warning:</h2> 2345 * If you think you want to do something like this, chances are that you really shouldn't be creating a singleton. 2346 * This pattern of initializing a singleton with "dynamic static data" should only be used if the purpose of the 2347 * class inherently makes the design require that there never be more than one instance, because multiple 2348 * instances would catastrophically interfere with each other or other data. See also the warning about 2349 * {@link $class.descriptor#$singleton}. 2350 * </p> 2351 * 2352 * <p> 2353 * <h2>Example:</h2> 2354 <pre class="brush:js"> 2355 $class("Example", { 2356 $singleton: { 2357 createInstance: function() { 2358 // A default value allows the code to not depend on an externally 2359 // defined value 2360 var paramValue = this.DEFAULT_PARAM_VALUE; 2361 2362 // If a global variable "EXAMPLE_PARAM_VALUE" exists, use its value. 2363 // The value of this variable could have been rendered by server 2364 // code (PHP, JSP, etc). This allows default behavior/configuration 2365 // to be overridden if desired. 2366 if (typeof EXAMPLE_PARAM_VALUE != "undefined") { 2367 paramValue = EXAMPLE_PARAM_VALUE; 2368 } 2369 2370 return new this(paramValue); 2371 }, 2372 }, 2373 2374 $constructor: function(param) { 2375 this._param = param; 2376 }, 2377 2378 getParam: function() { 2379 return this._param; 2380 }, 2381 2382 DEFAULT_PARAM_VALUE: $static(10) 2383 }); 2384 2385 var EXAMPLE_PARAM_VALUE = 5; 2386 alert(Example.getInstance().getParam()); // alerts "5" 2387 2388 EXAMPLE_PARAM_VALUE = 20; 2389 alert(Example.getInstance().getParam()); // still alerts "5" 2390 </pre> 2391 * </p> 2392 * 2393 * @type Function 2394 */ 2395 createInstance: null 2396 }; 2397 2398 /** 2399 * @name $class.descriptor.multiton_config 2400 * @class 2401 * <p> 2402 * This is a fake class to document the configuration object for use with {@link $class.descriptor#$multiton} 2403 * to setup a class as a multiton. If the defaults are acceptable, you can simply provide a value of 2404 * <em>true</em> instead of a multiton config object. 2405 * </p> 2406 */ 2407 $class_library._$multiton_config_docs = 2408 /** 2409 * @lends $class.descriptor.multiton_config.prototype 2410 */ 2411 { 2412 /** 2413 * <p> 2414 * A function that is used to create each multiton instance. This function receives all arguments that are 2415 * passed to the class's static getInstance method and is expected to return an instance of the class. The 2416 * function is called within the context of the class/constructor. Defaults to a function that simply 2417 * instantiates the class with the same arguments that were passed to getInstance(). 2418 * </p> 2419 * 2420 * <p> 2421 * I'm having a hard time coming up with a legitemate use for this property, but it seemed like it should be 2422 * supported to maintain consistency with {@link $class.descriptor.singleton_config}. Good luck :) 2423 * </p> 2424 * 2425 * @type Function 2426 */ 2427 createInstance: null, 2428 2429 /** 2430 * <p> 2431 * A function that is used to create a cache key for looking up cached multiton instances. This function 2432 * receives all arguments that are passed to the class's static getInstance method and is expected to return 2433 * a String that uniquely identifies the combination of arguments. The arguments should be "normalized", such 2434 * that if two different sets of arguments would be interpretted the same and result in equivalent instances, 2435 * an identical cache key should be generated for those two different sets of arguments. 2436 * </p> 2437 * 2438 * <p> 2439 * Defaults to a function that simply converts all arguments to Strings and joins them together with pipes (|). 2440 * </p> 2441 * 2442 * <p> 2443 * <h2>Example:</h2> 2444 <pre class="brush:js"> 2445 $class("Example", { 2446 $multiton: { 2447 createCacheKey: function(name) { 2448 // the cache key must account for any kind of 2449 // defaulting/processing of params in the constructor 2450 return name || "John Doe"; 2451 } 2452 }, 2453 2454 // name defaults to "John Doe" if empty or null 2455 $constructor: function(name) { 2456 this._name = name || "John Doe"; 2457 }, 2458 2459 getName() { 2460 return this._name; 2461 } 2462 }); 2463 2464 var example1 = Example.getInstance(null); 2465 var example2 = Example.getInstance("John Doe"); 2466 2467 // same name 2468 alert(example1.getName() == example2.getName()) // alerts "true" 2469 // same instance 2470 alert(example1 == example2) // alerts "true" 2471 </pre> 2472 * </p> 2473 * 2474 * @type Function 2475 */ 2476 createCacheKey: null 2477 }; 2478 2479 /** 2480 * @name $interface.descriptor 2481 * @class 2482 * <p> 2483 * This is a fake class used to document the descriptor object that is passed to the {@link $interface} 2484 * function to define an interface. The documented properties are handled specially as described by each property. 2485 * All possible property names beginning with a dollar sign ($), or an underscore and dollar sign (_$) are to 2486 * be considered reserved for use by the $class library. Anything else is fair game for defining properties 2487 * and methods in an interface. 2488 * </p> 2489 * 2490 * <p> 2491 * Unless documented otherwise, all properties in the descriptor are expected to describe interface methods. The 2492 * value of these properties must be a function, but its implementation will not be used, so don't bother writing 2493 * code in the body of the function. Providing a function value for these properties gives you a good place to 2494 * define and document params and return values. It also allows the debug version of the $class library to yell at 2495 * you if you try to create a non-static non-method property on an interface. 2496 * </p> 2497 * 2498 * <p> 2499 * <h2>Property Modifiers</h2> 2500 * The following modifiers can be applied to properties in the descriptor. See the documentation for each 2501 * modifier for details and examples. 2502 * <ul> 2503 * <li>{@link $static} - create a static property or method.</li> 2504 * </ul> 2505 * </p> 2506 */ 2507 $class_library._$interface_descriptor_docs = 2508 /** 2509 * @lends $interface.descriptor.prototype 2510 */ 2511 { 2512 /** 2513 * <p> 2514 * One or more interfaces to be inherited by this interface. Implementations of this interface will be considered 2515 * to be implementations of all inherited interfaces. All interface methods will be inherited, meaning that 2516 * implementations of this interface must implement all methods of all inherited interfaces in addition to any 2517 * methods defined by this interface. 2518 * </p> 2519 * 2520 * <p> 2521 * <h2>Debug version errors:</h2> 2522 * <ul> 2523 * <li>$extends is specified, but one of the values is null/undefined. This usually indicates that you attempted 2524 * to extend an interface and forgot to include the JS file that defines that interface, or maybe you 2525 * misspelled the interface name.</li> 2526 * <li>Attempt to extend something that is not an $interface.</li> 2527 * </ul> 2528 * </p> 2529 * 2530 * @type $interface|$interface[] 2531 */ 2532 $extends: null 2533 }; 2534