1 /* 2 * Copyright © 2009 Apple Inc. All rights reserved. 3 */ 4 5 /** 6 * @class 7 * 8 * <p>The TKClass function will process a constructor function and set up its inheritance chain, synthetize 9 * a number of its instance properties and mix in additional capabilities based properties defined on 10 * that function. The supported properties are:</p> 11 * 12 * <ul> 13 * <li><code>inherits</code> – the superclass</li> 14 * <li><code>includes</code> – an Array of modules to pull in</li> 15 * <li><code>synthetizes</code> – an Array of properties to synthetize</li> 16 * </ul> 17 * 18 * <p>For instance, consider the following class declaration:</p> 19 * 20 * <pre class="example"> 21 // extends MySuperClass 22 MyClass.inherits = MySuperClass; 23 24 // mixes in the TKEventTriage and TKPropertyTriage modules 25 MyClass.includes = [TKEventTriage, TKPropertyTriage]; 26 27 // synthetizes the .foo and .bar properties 28 MyClass.synthetizes = ['foo', 'bar']; 29 30 function MyClass () { 31 // constructor code goes here 32 }; 33 34 // process properties set up on the MyClass constructor 35 TKClass(MyClass); 36 * </pre> 37 * 38 * <p>Synthetization is fully automated as long as the class that wishes to offer synthetized properties follows 39 * the following rules:</p> 40 * 41 * <ul> 42 * <li>say the class wishes to synthetize the property <code>.foo</code></li> 43 * <li>if the class chooses to define a getter method, it must be defined as <code>getFoo</code> on its <code>prototype</code> 44 * <li>if the class chooses to define a setter method, it must be defined as <code>setFoo</code> on its <code>prototype</code> 45 * <li>the class must define an instance variable <code>._foo</code> which is the internal object that the class is responsible to update if either a setter is specified</li> 46 * </ul> 47 * 48 * <p>So our previous class declaration could be extended as follows:</p> 49 * 50 * <pre class="example"> 51 function MyClass () { 52 this._foo = ''; 53 }; 54 55 // custom setter for .foo, the getter is not specified 56 // and TKClass() will automatically create it 57 MyClass.prototype.setFoo = function (newFooValue) { 58 this._foo = newFooValue; 59 }; 60 61 // custom getter for .foo, the setter is not specified 62 // and TKClass() will automatically create it 63 MyClass.prototype.getBar = function (newFooValue) { 64 return 'Hello ' + this._foo; 65 }; 66 * </pre> 67 * 68 * @since TuneKit 1.0 69 * 70 * @constructor 71 * @param theClass {Object} The class. 72 */ 73 74 function TKClass (theClass) { 75 // check out if we have inheritance set up, otherwise, make TKObject the default superclass 76 if (TKUtils.objectIsUndefined(theClass.inherits) && theClass !== TKObject) { 77 theClass.inherits = TKObject; 78 } 79 80 // check out if we have mixins 81 if (theClass.includes) { 82 TKClass.mixin(theClass.prototype, theClass.includes); 83 } 84 85 // synthetizes properties defined locally 86 var properties = (theClass.synthetizes) ? theClass.synthetizes : []; 87 // now synthetize 88 for (var i = 0; i < properties.length; i++) { 89 TKClass.synthetizeProperty(theClass.prototype, properties[i], true); 90 } 91 92 // synthetizes properties by compiling them up the inheritance chain 93 var aClass = theClass; 94 var properties = []; 95 while (aClass.inherits) { 96 aClass = aClass.inherits; 97 if (aClass.synthetizes) { 98 properties = aClass.synthetizes.concat(properties); 99 } 100 } 101 // now synthetize 102 for (var i = 0; i < properties.length; i++) { 103 TKClass.synthetizeProperty(theClass.prototype, properties[i], false); 104 } 105 106 // go through each method and save its name as a custom property 107 // that we'll use later in TKObject.callSuper() 108 for (var i in theClass.prototype) { 109 // make sure we don't touch properties that were synthetized 110 if (theClass.prototype.__lookupGetter__(i)) { 111 continue; 112 } 113 var prop = theClass.prototype[i]; 114 if (TKUtils.objectIsFunction(prop)) { 115 prop._class = theClass; 116 prop._name = i; 117 prop.displayName = TKUtils.createDisplayName(theClass.name, i); 118 } 119 } 120 121 // inherit from the superclass 122 // default to TKObject if nothing is specified 123 if (theClass !== TKObject) { 124 theClass.prototype.__proto__ = theClass.inherits.prototype; 125 } 126 }; 127 128 TKClass.synthetizeProperty = function (object, propertyName, isPropertyInherited) { 129 var camel_ready = propertyName.charAt(0).toUpperCase() + propertyName.substr(1); 130 var getter_name = 'get' + camel_ready; 131 var setter_name = 'set' + camel_ready; 132 // check on function availability 133 var has_getter = TKUtils.objectHasMethod(object, getter_name); 134 var has_setter = TKUtils.objectHasMethod(object, setter_name); 135 136 // if we have neither a getter or a setter, then do nothing 137 // unless the property is defined locally 138 if (!isPropertyInherited && !(has_getter || has_setter)) { 139 return; 140 } 141 142 // assign the setter function if we have one 143 if (has_setter) { 144 var specified_setter_function = function (newValue) { 145 object[setter_name].call(this, newValue); 146 this.notifyPropertyChange(propertyName); 147 }; 148 specified_setter_function.displayName = 'Specified setter for .' + propertyName + ' on ' + object.constructor.name; 149 object.__defineSetter__(propertyName, specified_setter_function); 150 } 151 // otherwise just assign to _propertyName 152 else { 153 var default_setter_function = function (newValue) { 154 this['_' + propertyName] = newValue; 155 this.notifyPropertyChange(propertyName); 156 }; 157 default_setter_function.displayName = 'Default setter for .' + propertyName + ' on ' + object.constructor.name; 158 object.__defineSetter__(propertyName, default_setter_function); 159 } 160 161 // assign the getter function if we have one 162 if (has_getter) { 163 object.__defineGetter__(propertyName, object[getter_name]); 164 } 165 // otherwise just return _propertyName 166 else { 167 var default_getter_function = function () { 168 return this['_' + propertyName]; 169 }; 170 default_getter_function.displayName = 'Default getter for .' + propertyName + ' on ' + object.constructor.name; 171 object.__defineGetter__(propertyName, default_getter_function); 172 } 173 }; 174 175 TKClass.mixin = function (target, sources) { 176 for (var i = 0; i < sources.length; i++) { 177 TKUtils.copyPropertiesFromSourceToTarget(sources[i], target); 178 } 179 }; 180 181 TKUtils.setupDisplayNames(TKClass, 'TKClass'); 182