1 /** 2 * @fileOverview 3 * mixing.enchant.js 4 * <p>Ein Plugin für enchant.js mit dem es möglich ist, beliebig viele 5 * {@link enchant.Class} Klassen zusammen zu mischen. 6 * Um Copy & Paste zu vermeiden, ist es auch möglich Funktionen und 7 * Properties, die in einem Rezept ({@link enchant.Class.MixingRecipe}) 8 * definiert sind zu beliebig vielen Klassen hinzuzufügen.</p> 9 * <p>Durch dies ist es möglich ein Verhalten ähnlich zu multipler Vererbung zu erlangen.</p> 10 * <p>Benötigt:<ul> 11 * <li>enchant.js v0.6 oder höher.</li></ul></p> 12 * Weiterführende Informationen: {@link enchant.Class.mixClasses}, {@link enchant.Class.MixingRecipe.createFromClass}, 13 * {@link enchant.Class.mixClassesFromRecipe}, {@link enchant.Class.MixingRecipe} und 14 * {@link enchant.Class.applyMixingRecipe} 15 * @require enchant.js v0.6+ 16 * 17 * @version 0.1 18 * @author UEI Corporation (Kevin Kratzer) 19 **/ 20 21 if (enchant !== undefined) { 22 (function() { 23 24 /** 25 * @private 26 */ 27 var decorateFunctionFactory = function(srcFunction, currentFunctionName) { 28 return function() { 29 var firstResult, secondResult; 30 firstResult = this._mixing[currentFunctionName].apply(this,arguments); 31 secondResult = srcFunction.apply(this,arguments); 32 if(secondResult) { 33 return secondResult; 34 } 35 if(firstResult) { 36 return firstResult; 37 } 38 }; 39 }; 40 41 /** 42 * @private 43 */ 44 var voidFunction = function(){}; 45 46 /** 47 * @private 48 */ 49 var multipleMixingCombinationFunctionFactory = function(oldFunc,newFunc, key) { 50 return function() { 51 var firstResult = oldFunc.apply(this,arguments); 52 var mixingStore = this._mixing[key]; 53 this._mixing[key] = voidFunction; 54 var secondResult = newFunc.apply(this,arguments); 55 this._mixing[key] = mixingStore; 56 if(secondResult) { 57 return secondResult; 58 } 59 if(firstResult) { 60 return firstResult; 61 } 62 }; 63 }; 64 65 /** 66 * @private 67 */ 68 var createFromPrototypeNonRecursive = function(decorate, override, properties, source, functionOverrideNameList, functionIgnoreNameList, propertyIgnoreNameList) { 69 for(var key in source) { 70 if(source.hasOwnProperty(key)) { 71 var descriptor = Object.getOwnPropertyDescriptor(source, key); 72 if(descriptor.value && typeof(descriptor.value) === 'function') { 73 if((!functionIgnoreNameList || functionIgnoreNameList.indexOf(key) === -1) && key !== 'constructor') { 74 if(!functionOverrideNameList || functionOverrideNameList.indexOf(key) === -1) { 75 decorate[key] = (decorateFunctionFactory(source[key],key)); 76 } else { 77 override[key] = source[key]; 78 } 79 } 80 } else { 81 if(!propertyIgnoreNameList || propertyIgnoreNameList.indexOf(key) === -1 && key !== '_mixing') { 82 properties[key] = descriptor; 83 } 84 } 85 } 86 } 87 }; 88 89 /** 90 * @private 91 */ 92 var createFromPrototype = function(decorate,override,properties,source,onlyOwnProperties, functionOverrideNameList, functionIgnoreNameList, propertyIgnoreNameList) { 93 if(!onlyOwnProperties && source instanceof Object) { 94 createFromPrototype(decorate,override,properties,Object.getPrototypeOf(source),onlyOwnProperties, functionOverrideNameList, functionIgnoreNameList, propertyIgnoreNameList); 95 } 96 createFromPrototypeNonRecursive(decorate,override,properties,source, functionOverrideNameList, functionIgnoreNameList, propertyIgnoreNameList); 97 }; 98 99 /** 100 * @private 101 */ 102 var getFunctionParams = function(methodString) { 103 if(typeof(methodString) !== 'string') { 104 methodString = methodString.toString(); 105 } 106 return methodString.substring(methodString.indexOf('(')+1,methodString.indexOf(')')).replace(/\s+/,'').split(','); 107 }; 108 109 /*Public Interface */ 110 /** 111 * @scope enchant.Class.MixingRecipe.prototype 112 */ 113 enchant.Class.MixingRecipe = enchant.Class.create({ 114 /** 115 * Erstellt ein neues MixingRecipe, welches beschreibt, auf welche Art und Weise Funktionen und Properties während des Mischens hinzugefügt werden. 116 * Um ein Rezept aus einer bereits existierend Klasse zu erstellen, ist auf {@link enchant.Class.MixingRecipe.createFromClass} zu verweisen. 117 * @class Diese Klasse beschreibt auf welche Art und Weise das mischen mit der Zielklasse durchgeführt wird. 118 * Für diesen Zweck enthählt ein MixingRecipe drei Properties: 119 * <ul><li>decorateMethods (Methoden, welche in der Zielklasse dekoriert werden, siehe Dekorierer Entwurfsmuster)</li> 120 * <li>overrideMethods (Methoden, welche in der Zielklasse überschrieben werden)</li> 121 * <li>overrideProperties (Properties, welche in der Zielklasse redefiniert werden)</li></ul> 122 * <p>Siehe auch: {@link enchant.Class.mixClasses}, {@link enchant.Class.mixClassesFromRecipe} und {@link enchant.Class.applyMixingRecipe}.</p> 123 * @param {Object} decorateMethods Die Methoden, welche in der Zielklasse dekoriert werden, siehe Dekorierer Entwurfsmuster. Auf die dekoriertie Methode kann in der durch das Mixen erstellten Klasse mit Hilfe des _mixing Property zugegriffen werden, z.B. this._mixing.myFunction.apply(this,arguments).<br>(Objekt welches Schlüssel-Wert Paare enthält, Schlüssel := Funktionsname, Wert := Funktion) 124 * @param {Object} overrideMethods Die Methoden, welche in der Zielklasse überschrieben werden.<br>(Objekt welches Schlüssel-Wert Paare enthält, Schlüssel := Funktionsname, Wert := Funktion). 125 * @param {Object} properties Die Properties, welche in der Zielklasse redefiniert werden.<br>(Objekt welches Schlüssel-Wert Paare enthält, Schlüssel := Funktionsname, Wert := Propertydescriptor). 126 * @property {Object} decorateMethods Die Methoden, welche in der Zielklasse dekoriert werden, siehe Dekorierer Entwurfsmuster. Auf die dekoriertie Methode kann in der durch das Mixen erstellten Klasse mit Hilfe des _mixing Property zugegriffen werden, z.B. this._mixing.myFunction.apply(this,arguments).<br>(Objekt welches Schlüssel-Wert Paare enthält, Schlüssel := Funktionsname, Wert := Funktion) 127 * @property {Object} overrideMethods Die Methoden, welche in der Zielklasse überschrieben werden.<br>(Objekt welches Schlüssel-Wert Paare enthält, Schlüssel := Funktionsname, Wert := Funktion). 128 * @property {Object} overrideProperties Die Properties, welche in der Zielklasse redefiniert werden.<br>(Objekt welches Schlüssel-Wert Paare enthält, Schlüssel := Funktionsname, Wert := Propertydescriptor). 129 * @example 130 * var recipe = new enchant.Class.MixingRecipe({ 131 * add : function(value) { 132 * this._myValue += 3*value; 133 * this._mixing.add.apply(this,arguments); 134 * }, 135 * mult : function(value) { 136 * this._myValue *= value*7; 137 * this._mixing.mult.apply(this,arguments); 138 * } 139 * },{ 140 * sub : function(value) { 141 * this._myValue -= 5*value; 142 * } 143 * },{ 144 * myProperty : { 145 * get: function() { 146 * return 3*this._myPropertyValue; 147 * }, 148 * set : function(val) { 149 * this._myPropertyValue = val; 150 * } 151 * }}); 152 * var NewClass = enchant.Class.applyMixingRecipe(Class1,recipe); 153 * @extends Object 154 * @constructs 155 */ 156 initialize : function(decorateMethods, overrideMethods, properties) { 157 this.decorateMethods = decorateMethods; 158 this.overrideMethods = overrideMethods; 159 this.overrideProperties = properties; 160 } 161 }); 162 163 /** 164 * Nimmt die Methoden und Properties der übergebenen Klasse um ein neues MixingRecipe zu erstellen. 165 * Das Standardverhalten dabei ist, alle Funktionen und Properties der übergebenen Klasse, 166 * einschließlich der Funktionen und Properties in den Superklassen zu nehmen und diese 167 * in der Zielklasse beim mixen zu dekorieren.<br>Methoden welche dekoriert werden, rufen 168 * automatisch die Methoden der sourceClass und der Zielklasse des Mixens, mit Hilfe des 169 * _mixing Properties, auf. Daher muss dies nicht selbst berücksichtigt werden. 170 * <p>Durch die entsprechenden Argumente der Funktion kann das Standardverhalten zu verändert werden.</p> 171 * 172 * @param {Function<constructor Funktion die mit enchant.Class erstellt wurde>} sourceClass Die Klasse aus der das MixingRecipe erstellt wird. 173 * @param [boolean] onlyOwnProperties Wenn dieses Argument true ist, werden Funktionen und Properties der Superklassen ignoriert. 174 * @param [Array<String>] functionOverrideNameList Ein Array welches Namen von Funktionen enthält, welche während des Mixens überschrieben werden sollen. 175 * @param [Array<String>] functionIgnoreNameList Ein Array welches Namen von Funktionen enthält, welche bei der MixingRecipe erstellung ignoriert werden sollen. 176 * @param [Array<String>] propertyIgnoreNameList Ein Array welches Namen von Properties enthält, welche bei der MixingRecipe erstellung ignoriert werden sollen. 177 * @returns {enchant.Class.MixingRecipe} Das MixingRecipe, welches aus der Definition der sourceClass erstellt wurde. 178 * @example 179 * var recipe = enchant.Class.MixingRecipe.createFromClass(Class2, true, 180 * ['overrideFunction1','overrideFunction2'], 181 * ['ignoreFunction1','ignoreFunction2'], 182 * ['ignoreProperty1','ignorePropterty2']); 183 * recipe.overrideMethods['additionalFunction'] = new function() { 184 * console.log('Hello, World'); 185 * } 186 * recipe.overrideProperties['newProperty'] = { 187 * get: function() { 188 * return this._newProperty; 189 * }, 190 * set : function(val) { 191 * this._newProperty = val; 192 * } 193 * } 194 * var NewClass = enchant.Class.mixClassesFromRecipe(Class1,Class2,recipe); 195 * @constructs 196 * @static 197 */ 198 enchant.Class.MixingRecipe.createFromClass = function(sourceClass, onlyOwnProperties, functionOverrideNameList, functionIgnoreNameList, propertyIgnoreNameList) { 199 var decorate = {}; 200 var override = {}; 201 var properties = {}; 202 203 var source = sourceClass.prototype; 204 createFromPrototype(decorate,override,properties,source,onlyOwnProperties, functionOverrideNameList, functionIgnoreNameList, propertyIgnoreNameList); 205 return new enchant.Class.MixingRecipe(decorate,override,properties); 206 }; 207 208 /** 209 * Nutzt das gegebene MixingRecipe, wendet es auf die "firstClass" Klasse an und liefert das Ergebnis daraus zurück - das MixingRecipe sollte der secondClass entsprechen. 210 * Eine Standard-Initialisierungsmethode wird hinzugefügt, welche die Initialisierungsmethode beider Klassen aufruft. 211 * Die Signatur für diese Standard-Initialisierungsmethode ist:<br> 212 * ([firstClass Konstruktor Arg 1],...,[firstClass Konstruktor Arg n],[secondClass Konstruktor Arg 1],...[secondClass Konstruktor Arg n])</p> 213 * <p>Beide Klassen werden nicht verändert.</p> Siehe auch: {@link enchant.Class.MixingRecipe} 214 * @param {Function<constructor Funktion die mit enchant.Class erstellt wurde>} firstClass Die Klasse auf die das MixingRecipe angewendet wird. 215 * @param {Function<constructor Funktion die mit enchant.Class erstellt wurde>} secondClass Die Klasse die dem MixingRecipe entpsricht, wird für die Standard-Initialisierungsmethode genutzt. 216 * @param {enchant.Class.MixingRecipe} recipe Das MixingRecipe, welches auf die firstClass Klasse angewendet wird - sollte der secondClass entsprechen. 217 * @param [Function] initializeMethod Falls gegeben, wird diese Methode, anstelle der Standard-Initialisierungsmethode, zum Initialisieren der resultierenden Klasse verwendet. 218 * @returns {Function<constructor Funktion die mit enchant.Class erstellt wurde>} Die Klasse, welche das Ergebnis des Mixens beider Klassen mit Hilfe des MixingRecipe darstellt. 219 * @example 220 * var MapGroup = enchant.Class.mixClasses(Map, Group,true); 221 * var map = new MapGroup(16, 16); 222 * var SpriteLabel = enchant.Class.mixClasses(Sprite, Label,true); 223 * var kumaLabel = new SpriteLabel(32,32,'Kuma'); 224 * @static 225 */ 226 enchant.Class.mixClassesFromRecipe = function(firstClass, secondClass, recipe, initializeMethod) { 227 var result = enchant.Class.applyMixingRecipe(firstClass,recipe); 228 var paramLength = getFunctionParams(firstClass.prototype.initialize).length; 229 if(typeof(initializeMethod) !== 'function') { 230 initializeMethod = function() { 231 var args = Array.prototype.slice.call(arguments); 232 secondClass.prototype.initialize.apply(this,args.slice(paramLength)); 233 firstClass.prototype.initialize.apply(this,args.slice(0,paramLength)); 234 }; 235 } 236 result.prototype.initialize = initializeMethod; 237 return result; 238 }; 239 240 241 /** 242 * Erstellt ein MixingRecipe aus der "secondClass" Klasse, wendet dieses auf die "firstClass" Klasse an und liefert das Ergebnis daraus zurück. 243 * Das Standardverhalten dabei ist, alle Funktionen und Properties der "secondClass" Klasse, 244 * einschließlich der Funktionen und Properties in deren Superklassen zu nehmen und diese 245 * in der Zielklasse beim mixen zu dekorieren.<br>Methoden welche dekoriert werden, rufen 246 * automatisch die Methoden der sourceClass und der Zielklasse des Mixens, mit Hilfe des 247 * _mixing Properties, auf. Daher muss dies nicht selbst berücksichtigt werden. 248 * <p>Des Weiteren wird eine Standard-Initialisierungsmethode, welche die Initialisierungsmethode beider Klassen aufruft, hinzugefügt. 249 * Die Signatur für diese Standard-Initialisierungsmethode ist:<br> 250 * ([firstClass Konstruktor Arg 1],...,[firstClass Konstruktor Arg n],[secondClass Konstruktor Arg 1],...[secondClass Konstruktor Arg n])</p> 251 * <p>Beide Klassen werden nicht verändert.</p> Siehe auch: {@link enchant.Class.MixingRecipe} 252 * 253 * @param {Function<constructor Funktion die mit enchant.Class erstellt wurde>} firstClass Die Klasse auf die das MixingRecipe angewendet wird. 254 * @param {Function<constructor Funktion die mit enchant.Class erstellt wurde>} secondClass Die Klasse aus der das MixingRecipe erstellt wird. 255 * @param [boolean] onlyOwnProperties Wenn dieses Argument true ist, werden Funktionen und Properties der Superklassen der "secondClass" Klasse ignoriert. 256 * @param [Function] initializeMethod Falls gegeben, wird diese Methode, anstelle der Standard-Initialisierungsmethode, zum Initialisieren der resultierenden Klasse verwendet. 257 * @returns {Function<constructor Funktion die mit enchant.Class erstellt wurde>} Die Klasse, welche das Ergebnis des Mixens beider Klassen darstellt. 258 * @example 259 * var MapGroup = enchant.Class.mixClasses(Map, Group,true); 260 * var map = new MapGroup(16, 16); 261 * var SpriteLabel = enchant.Class.mixClasses(Sprite, Label,true); 262 * var kumaLabel = new SpriteLabel(32,32,'Kuma'); 263 * @static 264 */ 265 enchant.Class.mixClasses = function(firstClass, secondClass, useOnlyOwnPropertiesForSecondClass, initializeMethod) { 266 return enchant.Class.mixClassesFromRecipe(firstClass,secondClass,enchant.Class.MixingRecipe.createFromClass(secondClass, useOnlyOwnPropertiesForSecondClass, [], ['initialize'], []),initializeMethod); 267 }; 268 269 /** 270 * Wendet das übergebene MixingRecipe auf die Zielklasse an, erstellt eine neue Klassendefinition und liefert diese als Rückgabewert zurück. 271 * Die Zielklasse wird nicht modifiziert.<br>Siehe auch: {@link enchant.Class.MixingRecipe}. 272 * 273 * @param {Function<constructor Funktion die mit enchant.Class erstellt wurde>} target Die Klasse auf die das MixingRecipe angewendet wird. 274 * @param {enchant.Class.MixingRecipe} source Das MixingRecipe, welches genutzt wird um der Zielklasse neue Funktionalität zu verleihen. 275 * @returns {Function<constructor Funktion die mit enchant.Class erstellt wurde>} Die Klasse, welche das Ergebnis des Mixens der Zielklasse (target) mit dem Rezept (source) darstellt. 276 * @example 277 * var recipe = new enchant.Class.MixingRecipe({ 278 * // ... see enchant.Class.MixingRecipe 279 * },{ 280 * // ... see enchant.Class.MixingRecipe 281 * },{ 282 * // ... see enchant.Class.MixingRecipe 283 * }); 284 * var NewClass = applyMixingRecipe(Class1,recipe); 285 * @static 286 */ 287 enchant.Class.applyMixingRecipe = function(target, source) { 288 var result = enchant.Class.create(target,{}); 289 target = result.prototype; 290 for(var recipeKey in source) { 291 if(source.hasOwnProperty(recipeKey)) { 292 var currentSource = source[recipeKey]; 293 if(recipeKey === 'overrideMethods') { 294 for(var methodKey in currentSource) { 295 if(currentSource.hasOwnProperty(methodKey)) { 296 target[methodKey] = currentSource[methodKey]; 297 if(target._mixing && target._mixing[methodKey]) { 298 target._mixing[methodKey] = voidFunction; 299 } 300 } 301 } 302 } else if(recipeKey === 'overrideProperties') { 303 for(var propertyKey in currentSource) { 304 if(currentSource.hasOwnProperty(propertyKey)) { 305 Object.defineProperty(target,propertyKey,currentSource[propertyKey]); 306 } 307 } 308 } else if(recipeKey === 'decorateMethods') { 309 if(!target._mixing) { 310 target._mixing = {}; 311 } 312 for(var key in currentSource) { 313 if(currentSource.hasOwnProperty(key)) { 314 var targetHolder = target; 315 if(!target[key]) { 316 while(targetHolder instanceof Object && !targetHolder[key]) { 317 targetHolder = Object.getPrototypeOf(targetHolder); 318 } 319 } 320 if(target._mixing[key]) { 321 var newFunc = targetHolder[key]; 322 target._mixing[key] = (multipleMixingCombinationFunctionFactory(target._mixing[key],newFunc,key)); 323 } else { 324 target._mixing[key] = targetHolder[key]; 325 if(!target._mixing[key]) { 326 target._mixing[key] = voidFunction; 327 } 328 } 329 target[key] = currentSource[key]; 330 } 331 } 332 } 333 } 334 } 335 return result; 336 }; 337 })(); 338 } 339