1 /** 2 * The Render Engine 3 * BaseObject 4 * 5 * @fileoverview The object from which most renderable engine objects will 6 * need to derive. 7 * 8 * @author: Brett Fattori (brettf@renderengine.com) 9 * @author: $Author: bfattori $ 10 * @version: $Revision: 1573 $ 11 * 12 * Copyright (c) 2011 Brett Fattori (brettf@renderengine.com) 13 * 14 * Permission is hereby granted, free of charge, to any person obtaining a copy 15 * of this software and associated documentation files (the "Software"), to deal 16 * in the Software without restriction, including without limitation the rights 17 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 * copies of the Software, and to permit persons to whom the Software is 19 * furnished to do so, subject to the following conditions: 20 * 21 * The above copyright notice and this permission notice shall be included in 22 * all copies or substantial portions of the Software. 23 * 24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 30 * THE SOFTWARE. 31 * 32 */ 33 34 // The class this file defines and its required classes 35 R.Engine.define({ 36 "class":"R.engine.BaseObject", 37 "requires":[ 38 "R.engine.PooledObject", 39 "R.engine.Events" 40 ] 41 }); 42 43 /** 44 * @class The base object class which represents an object within 45 * the engine. Objects of this type can have an associated DOM element, 46 * and will also inherit the {@link #update} method to perform processing 47 * for every frame generated. 48 * <p/> 49 * This object also enhances the event handling provided by the {@link R.engine.Events event engine}. 50 * It will remember events assigned to the object so that they can be automatically 51 * cleaned up when the object is destroyed. 52 * <p/> 53 * If you are working with an object that represents an HTML element (node), 54 * this object is ideal to extend from. It has methods for assigning and 55 * accessing that element. 56 * <p/> 57 * The {@link #update} method is called each time a frame is generated by the engine 58 * to update the object within the scene graph. In this method, you'll be able to 59 * update the internals of the object and perform general housekeeping. 60 * 61 * @param name {String} The name of the object 62 * @extends R.engine.PooledObject 63 * @constructor 64 * @description Create a base object. 65 */ 66 R.engine.BaseObject = function () { 67 "use strict"; 68 return R.engine.PooledObject.extend(/** @scope R.engine.BaseObject.prototype */{ 69 70 element:null, 71 jQObject:undefined, 72 events:null, 73 eventListeners:null, 74 75 /** @private */ 76 constructor:function (name) { 77 this.base(name); 78 this.events = {}; 79 this.eventListeners = {}; 80 this.jQObject = undefined; 81 }, 82 83 /** 84 * Destroy the object, cleaning up any events that have been 85 * attached to this object. Calls the <tt>destroy</tt> event 86 * before the object is destroyed. 87 */ 88 destroy:function () { 89 // Trigger the "destroy" event before the object is destroyed 90 this.triggerEvent("destroyed"); 91 92 // We need to make sure to remove any event's attached to us 93 // that weren't already cleaned up 94 for (var ref in this.events) { 95 var fn = this.events[ref]; 96 var type = ref.split(",")[1]; 97 if (fn) { 98 R.engine.Events.clearHandler(this.getElement(), type, fn); 99 } 100 } 101 102 // Clean up non-element event listeners 103 this.eventListeners = {}; 104 105 if (this.element != null && this.element != document) { 106 $(this.element).empty().remove(); 107 } 108 109 this.base(); 110 }, 111 112 /** 113 * Release the object back into the object pool. 114 */ 115 release:function () { 116 this.base(); 117 this.element = null; 118 this.events = null; 119 this.eventListeners = null; 120 this.jQObject = undefined; 121 }, 122 123 /** 124 * Set the element which will represent this object within 125 * its rendering context. 126 * 127 * @param element {HTMLElement} The HTML element this object is associated with. 128 */ 129 setElement:function (element) { 130 // If it isn't an element (prolly jQuery) then get the root element in the jQuery object 131 this.element = element.nodeType ? element : element[0]; 132 }, 133 134 /** 135 * Get the element which represents this object within its rendering context. 136 * 137 * @return {HTMLElement} The HTML element 138 */ 139 getElement:function () { 140 return this.element; 141 }, 142 143 /** 144 * A helper method to provide access to the jQuery object wrapping the 145 * element for this object. This allows direct access to the DOM. 146 * 147 * @return {jQuery} A jQuery object 148 */ 149 jQ:function () { 150 if (!this.jQObject) { 151 this.jQObject = this.element ? $(this.element) : null; 152 } 153 return this.jQObject; 154 }, 155 156 /** 157 * Abstract update method to set the state of the object. This method 158 * will be called each frame that is generated by the engine. The 159 * context where the object will be rendered is passed, along with the 160 * current engine time. Use this method to update components and 161 * perform housekeeping on the object. 162 * 163 * @param renderContext {R.rendercontexts.AbstractRenderContext} The context the object exists within 164 * @param time {Number} The current engine time, in milliseconds 165 * @param dt {Number} The delta between the world time and the last time the world was updated 166 * in milliseconds. 167 */ 168 update:function (renderContext, time, dt) { 169 }, 170 171 /** 172 * Add an event handler to this object. Within the 173 * event handler, <tt>this</tt> refers to the object upon which the event is being triggered. It is 174 * possible to bind an event simply by calling <tt>addEvent</tt> with the type and callback, like 175 * so: 176 * <pre> 177 * this.addEvent("click", function(evt) { 178 * this.doSomething(evt); 179 * }); 180 * </pre> 181 * However, if you need to reference another object during the binding process, such as when 182 * a render context is binding an event to a game object, you could pass a reference object 183 * as the first argument: 184 * <pre> 185 * // "this" refers to the render context 186 * someObj.addEvent(this, "click", function(evt) { 187 * // Inside the handler, "this" is the target of the event 188 * this.doSomething(evt); 189 * }); 190 * </pre> 191 * The purpose behind this is that if the render context assigned the event, it should 192 * probably remove the handler, rather than the game object needing to remove the handler. 193 * But, if the game object <i>also</i> has a "click" handler, you don't want to remove 194 * <i>that handler</i> since the game object may still need it. 195 * <p/> 196 * If the event handler explicitly returns <code>false</code> (not <code>null</code>, or 197 * <code>undefined</code>), further event handlers will not be processed. 198 * 199 * @param [ref] {Object} The object reference which is assigning the event 200 * @param type {String} The event type to respond to 201 * @param [data] {Array} Optional data to pass to the handler when it is invoked. 202 * @param fn {Function} The function to trigger when the event fires 203 */ 204 addEvent:function (ref, type, data, fn) { 205 206 // CAUTION: Brain Teaser 207 fn = R.isString(ref) ? (R.isFunction(type) ? type : (R.isFunction(data) ? data : fn)) : 208 R.isFunction(data) ? data : fn; 209 data = R.isString(ref) ? (R.isFunction(type) ? null : (R.isFunction(data) ? null : data)) : 210 R.isFunction(data) ? null : data; 211 type = R.isString(ref) ? ref : type; 212 ref = R.isString(ref) ? this : ref; 213 // CAUTION ------------- 214 215 if (ref == null) { 216 // This is a global assignment to the document body. Many listeners 217 // may collect data from the event handler. 218 R.debug.Console.info("Global assignment of event '" + type + "'"); 219 R.engine.Events.setHandler(document.body, type, data || fn, fn); 220 this.events["document," + type] = fn; 221 } 222 else { 223 R.debug.Console.debug(ref.getName() + " attach event '" + type + "' to " + this.getName()); 224 if (this.getElement() && ref instanceof R.rendercontexts.AbstractRenderContext) { 225 R.engine.Events.setHandler(this.getElement(), type, data || fn, fn); 226 227 // Remember the handler by the reference object's name and event type 228 this.events[ref.getName() + "," + type] = fn; 229 } else { 230 // We want to be able to add event handlers to objects which don't 231 // have an element associated with them as well 232 var listeners = this.eventListeners[type.toUpperCase()]; 233 if (!listeners) { 234 listeners = this.eventListeners[type.toUpperCase()] = []; 235 } 236 237 // Add the new listener 238 listeners.push({ 239 id:ref.getName() + "," + type, 240 data:data, 241 callback:fn 242 }); 243 } 244 } 245 }, 246 247 /** 248 * Helper method to simplify wiring event handlers to event types. You can assign 249 * multiple handlers, by name, using object or array notation, for example: 250 * <pre> 251 * // Shorthand 252 * this.addEvents({ 253 * "keydown": function(evt, which) { 254 * this.onKeyDown(which); 255 * }, 256 * "keyup": function(evt, which) { 257 * this.onKeyUp(which); 258 * } 259 * }); 260 * 261 * // Super shorthand 262 * this.addEvents(["onKeyDown", "onKeyUp"]); 263 * </pre> 264 * Object notation allows you to still be explicit in which handler method to 265 * apply or call, and the arguments which are handled. Array notation will 266 * simply create a method call which takes the names of the event handlers, 267 * removes "on" and then lower cases the remainder to create the event assignment. 268 * <p/> 269 * In the example above, <tt>onKeyDown</tt> is the name of your method to be 270 * called, and <tt>keydown</tt> will be the name of the actual event. 271 * 272 * @param handlers {Object|Array} The event assignments 273 */ 274 addEvents:function (handlers) { 275 var self = this; 276 if ($.isArray(handlers)) { 277 for (var h = 0; h < handlers.length; h++) { 278 var method = this[handlers[h]]; 279 this.addEvent(handlers[h].substr(2).toLowerCase(), method); 280 } 281 } else { 282 $.each(handlers, function (key, value) { 283 self.addEvent(key, value); 284 }); 285 } 286 }, 287 288 /** 289 * Remove the event handler assigned to the object for the given type. The optional 290 * <tt>ref</tt> argument is used when another object assigned the event handler, such as: 291 * <pre> 292 * // Handler #1 293 * someObject.addEvent("click", function(evt) { 294 * this.doSomething(evt); 295 * }); 296 * 297 * // Handler #2 298 * someObject.addEvent(anotherObject, "click", function(evt) { 299 * this.doSomething(evt); 300 * }); 301 * </pre> 302 * You would remove the "click" handler that <tt>anotherObject</tt> assigned (handler #2), 303 * and not one that was bound by <tt>someObject</tt> (handler #1): 304 * <pre> 305 * someObject.removeEvent(anotherObject, "click"); 306 * </pre> 307 * 308 * @param [ref] {Object} The object reference which assigned the event 309 * @param type {String} The event type to remove 310 */ 311 removeEvent:function (ref, type) { 312 var fn; 313 ref = R.isString(ref) ? this : ref; 314 if (ref == null) { 315 // This was a global assignment to the document body. Clean it up 316 R.debug.Console.info("Global event '" + type + "' removed"); 317 fn = this.events["document," + type]; 318 R.engine.Events.clearHandler(document.body, type, fn); 319 } 320 else { 321 R.debug.Console.info(ref.getName() + " remove event '" + type + "' from " + this.getName()); 322 var id = ref.getName() + "," + type; 323 if (this.getElement() && ref instanceof R.rendercontexts.AbstractRenderContext) { 324 // Find the handler to remove 325 fn = this.events[id]; 326 if (fn) { 327 R.engine.Events.clearHandler(this.getElement(), type, fn); 328 } 329 // Remove the reference 330 delete this.events[ref.getName() + "," + type]; 331 } else { 332 var listeners = this.eventListeners[type.toUpperCase()]; 333 if (listeners) { 334 listeners = R.engine.Support.filter(listeners, function (e) { 335 return e.id !== id; 336 }); 337 } 338 } 339 } 340 }, 341 342 /** 343 * Trigger an event on the object. Event handlers assigned with 344 * {@link #addEvent} will be triggered and passed the event object 345 * and the data. The scope of the listener function will be the 346 * object. 347 * 348 * @param eventName {String} The event to trigger 349 * @param [eventObj] {Event} The original event object 350 * @param data {Array} An array of data to pass to the event handler 351 */ 352 triggerEvent:function (eventName, eventObj, data) { 353 var ret; 354 if (this.getElement() && this instanceof R.rendercontexts.AbstractRenderContext) { 355 if (eventObj && (eventObj instanceof Event)) { 356 ret = this.jQ().trigger(eventObj); 357 } else { 358 ret = this.jQ().trigger(eventName, eventObj); 359 } 360 return ret 361 } else { 362 if (this.eventListeners) { 363 var listeners = this.eventListeners[eventName.toUpperCase()]; 364 if (listeners) { 365 if (eventObj && R.isArray(eventObj)) { 366 data = eventObj; 367 eventObj = $.Event({ type:eventName }); 368 } 369 data = data || []; 370 371 // Make sure the first element is the event 372 data.unshift(eventObj); 373 374 for (var e = 0; e < listeners.length; e++) { 375 var listener = listeners[e]; 376 377 // Append the predefined listener data to the data object 378 if (listener.data) { 379 data = data.concat(listener.data); 380 } 381 382 // Call the listener 383 ret = listener.callback.apply(this, data); 384 if (ret === false) { 385 return false; 386 } 387 } 388 } 389 } 390 } 391 } 392 393 }, /** @scope R.engine.BaseObject.prototype */ { 394 395 /** 396 * Get the class name of this object 397 * 398 * @return {String} "R.engine.BaseObject" 399 */ 400 getClassName:function () { 401 return "R.engine.BaseObject"; 402 } 403 404 }); 405 406 };