1//////////////////////////////////////////////////////////////////////////////// 2// 3// ADOBE SYSTEMS INCORPORATED 4// Copyright 2005-2007 Adobe Systems Incorporated 5// All Rights Reserved. 6// 7// NOTICE: Adobe permits you to use, modify, and distribute this file 8// in accordance with the terms of the license agreement accompanying it. 9// 10//////////////////////////////////////////////////////////////////////////////// 11 12package mx.utils 13{ 14 15import flash.events.Event; 16import flash.events.EventDispatcher; 17import flash.utils.getQualifiedClassName; 18import flash.utils.IDataInput; 19import flash.utils.IDataOutput; 20import flash.utils.IExternalizable; 21import flash.utils.Proxy; 22import flash.utils.flash_proxy; 23import mx.core.IPropertyChangeNotifier; 24import mx.events.PropertyChangeEvent; 25import mx.events.PropertyChangeEventKind; 26 27use namespace flash_proxy; 28use namespace object_proxy; 29 30[Bindable("propertyChange")] 31[RemoteClass(alias="flex.messaging.io.ObjectProxy")] 32 33/** 34 * This class provides the ability to track changes to an item 35 * managed by this proxy. 36 * Any number of objects can "listen" for changes on this 37 * object, by using the <code>addEventListener()</code> method. 38 * 39 * @example 40 * <pre> 41 * import mx.events.PropertyChangeEvent; 42 * import mx.utils.ObjectUtil; 43 * import mx.utils.ObjectProxy; 44 * import mx.utils.StringUtil; 45 * 46 * var a:Object = { name: "Tyler", age: 5, ssnum: "555-55-5555" }; 47 * var p:ObjectProxy = new ObjectProxy(a); 48 * p.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, updateHandler); 49 * p.name = "Jacey"; 50 * p.age = 2; 51 * delete p.ssnum; 52 * 53 * // handler function 54 * function updateHandler(event:ChangeEvent):void 55 * { 56 * trace(StringUtil.substitute("updateHandler('{0}', {1}, {2}, {3}, '{4}')", 57 * event.kind, 58 * event.property, 59 * event.oldValue, 60 * event.newValue, 61 * event.target.object_proxy::UUID)); 62 * } 63 * 64 * // The trace output appears as: 65 * // updateHandler('opUpdate', name, Jacey, '698AF8CB-B3D9-21A3-1AFFDGHT89075CD2') 66 * // updateHandler('opUpdate', age, 2, '698AF8CB-B3D9-21A3-1AFFDGHT89075CD2') 67 * // updateHandler('opDelete', ssnum, null, '698AF8CB-B3D9-21A3-1AFFDGHT89075CD2') 68 * </pre> 69 */ 70public dynamic class ObjectProxy extends Proxy 71 implements IExternalizable, 72 IPropertyChangeNotifier 73{ 74 //-------------------------------------------------------------------------- 75 // 76 // Constructor 77 // 78 //-------------------------------------------------------------------------- 79 80 /** 81 * Initializes this proxy with the specified object, id and proxy depth. 82 * 83 * @param item Object to proxy. 84 * If no item is specified, an anonymous object will be constructed 85 * and assigned. 86 * 87 * @param uid String containing the unique id 88 * for this object instance. 89 * Required for IPropertyChangeNotifier compliance as every object must 90 * provide a unique way of identifying it. 91 * If no value is specified, a random id will be assigned. 92 * 93 * @param proxyDepth An integer indicating how many levels in a complex 94 * object graph should have a proxy created during property access. 95 * The default is -1, meaning "proxy to infinite depth". 96 * 97 * @example 98 * 99 * <pre> 100 * import mx.events.PropertyChangeEvent; 101 * import mx.utils.ObjectUtil; 102 * import mx.utils.ObjectProxy; 103 * import mx.utils.StringUtil; 104 * 105 * var a:Object = { name: "Tyler", age: 5, ssnum: "555-55-5555" }; 106 * var p:ObjectProxy = new ObjectProxy(a); 107 * p.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, updateHandler); 108 * p.name = "Jacey"; 109 * p.age = 2; 110 * delete p.ssnum; 111 * 112 * // handler function 113 * function updateHandler(event:PropertyChangeEvent):void 114 * { 115 * trace(StringUtil.substitute("updateHandler('{0}', {1}, {2}, {3}, '{4}')", 116 * event.kind, 117 * event.property, 118 * event.oldValue, 119 * event.newValue, 120 * event.target.uid)); 121 * } 122 * 123 * // trace output 124 * updateHandler('opUpdate', name, Jacey, '698AF8CB-B3D9-21A3-1AFFDGHT89075CD2') 125 * updateHandler('opUpdate', age, 2, '698AF8CB-B3D9-21A3-1AFFDGHT89075CD2') 126 * updateHandler('opDelete', ssnum, null, '698AF8CB-B3D9-21A3-1AFFDGHT89075CD2') 127 * </pre> 128 */ 129 public function ObjectProxy(item:Object = null, uid:String = null, 130 proxyDepth:int = -1) 131 { 132 super(); 133 134 if (!item) 135 item = {}; 136 _item = item; 137 138 _proxyLevel = proxyDepth; 139 140 notifiers = {}; 141 142 dispatcher = new EventDispatcher(this); 143 144 // If we got an id, use it. Otherwise the UID is lazily 145 // created in the getter for UID. 146 if (uid) 147 _id = uid; 148 } 149 150 //-------------------------------------------------------------------------- 151 // 152 // Variables 153 // 154 //-------------------------------------------------------------------------- 155 156 /** 157 * A reference to the EventDispatcher for this proxy. 158 */ 159 protected var dispatcher:EventDispatcher; 160 161 /** 162 * A hashmap of property change notifiers that this proxy is 163 * listening for changes from; the key of the map is the property name. 164 */ 165 protected var notifiers:Object; 166 167 /** 168 * Indicates what kind of proxy to create 169 * when proxying complex properties. 170 * Subclasses should assign this value appropriately. 171 */ 172 protected var proxyClass:Class = ObjectProxy; 173 174 /** 175 * Contains a list of all of the property names for the proxied object. 176 * Descendants need to fill this list by overriding the 177 * <code>setupPropertyList()</code> method. 178 */ 179 protected var propertyList:Array; 180 181 /** 182 * Indicates how deep proxying should be performed. 183 * If -1 (default), always proxy; 184 * if this value is zero, no proxying will be performed. 185 */ 186 private var _proxyLevel:int; 187 188 //-------------------------------------------------------------------------- 189 // 190 // Properties 191 // 192 //-------------------------------------------------------------------------- 193 194 //---------------------------------- 195 // object 196 //---------------------------------- 197 198 /** 199 * Storage for the object property. 200 */ 201 private var _item:Object; 202 203 /** 204 * The object being proxied. 205 */ 206 object_proxy function get object():Object 207 { 208 return _item; 209 } 210 211 //---------------------------------- 212 // type 213 //---------------------------------- 214 215 /** 216 * @private 217 * Storage for the qualified type name. 218 */ 219 private var _type:QName; 220 221 /** 222 * The qualified type name associated with this object. 223 */ 224 object_proxy function get type():QName 225 { 226 return _type; 227 } 228 229 /** 230 * @private 231 */ 232 object_proxy function set type(value:QName):void 233 { 234 _type = value; 235 } 236 237 //---------------------------------- 238 // uid 239 //---------------------------------- 240 241 /** 242 * @private 243 * Storage for the uid property. 244 */ 245 private var _id:String; 246 247 /** 248 * The unique identifier for this object. 249 */ 250 public function get uid():String 251 { 252 if (_id === null) 253 _id = UIDUtil.createUID(); 254 255 return _id; 256 } 257 258 /** 259 * @private 260 */ 261 public function set uid(value:String):void 262 { 263 _id = value; 264 } 265 266 //-------------------------------------------------------------------------- 267 // 268 // Overridden methods 269 // 270 //-------------------------------------------------------------------------- 271 272 /** 273 * Returns the specified property value of the proxied object. 274 * 275 * @param name Typically a string containing the name of the property, 276 * or possibly a QName where the property name is found by 277 * inspecting the <code>localName</code> property. 278 * 279 * @return The value of the property. 280 * In some instances this value may be an instance of 281 * <code>ObjectProxy</code>. 282 */ 283 override flash_proxy function getProperty(name:*):* 284 { 285 // if we have a data proxy for this then 286 var result:*; 287 288 if (notifiers[name.toString()]) 289 return notifiers[name]; 290 291 result = _item[name]; 292 293 if (result) 294 { 295 if (_proxyLevel == 0 || ObjectUtil.isSimple(result)) 296 { 297 return result; 298 } 299 else 300 { 301 result = object_proxy::getComplexProperty(name, result); 302 } // if we are proxying 303 } 304 305 return result; 306 } 307 308 /** 309 * Returns the value of the proxied object's method with the specified name. 310 * 311 * @param name The name of the method being invoked. 312 * 313 * @param rest An array specifying the arguments to the 314 * called method. 315 * 316 * @return The return value of the called method. 317 */ 318 override flash_proxy function callProperty(name:*, ... rest):* 319 { 320 return _item[name].apply(_item, rest) 321 } 322 323 /** 324 * Deletes the specified property on the proxied object and 325 * sends notification of the delete to the handler. 326 * 327 * @param name Typically a string containing the name of the property, 328 * or possibly a QName where the property name is found by 329 * inspecting the <code>localName</code> property. 330 * 331 * @return A Boolean indicating if the property was deleted. 332 */ 333 override flash_proxy function deleteProperty(name:*):Boolean 334 { 335 var notifier:IPropertyChangeNotifier = IPropertyChangeNotifier(notifiers[name]); 336 if (notifier) 337 { 338 notifier.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, 339 propertyChangeHandler); 340 delete notifiers[name]; 341 } 342 343 var oldVal:* = _item[name]; 344 var deleted:Boolean = delete _item[name]; 345 346 if (dispatcher.hasEventListener(PropertyChangeEvent.PROPERTY_CHANGE)) 347 { 348 var event:PropertyChangeEvent = new PropertyChangeEvent(PropertyChangeEvent.PROPERTY_CHANGE); 349 event.kind = PropertyChangeEventKind.DELETE; 350 event.property = name; 351 event.oldValue = oldVal; 352 event.source = this; 353 dispatcher.dispatchEvent(event); 354 } 355 356 return deleted; 357 } 358 359 /** 360 * @private 361 */ 362 override flash_proxy function hasProperty(name:*):Boolean 363 { 364 return(name in _item); 365 } 366 367 /** 368 * @private 369 */ 370 override flash_proxy function nextName(index:int):String 371 { 372 return propertyList[index -1]; 373 } 374 375 /** 376 * @private 377 */ 378 override flash_proxy function nextNameIndex(index:int):int 379 { 380 if (index == 0) 381 { 382 setupPropertyList(); 383 } 384 385 if (index < propertyList.length) 386 { 387 return index + 1; 388 } 389 else 390 { 391 return 0; 392 } 393 } 394 395 /** 396 * @private 397 */ 398 override flash_proxy function nextValue(index:int):* 399 { 400 return _item[propertyList[index -1]]; 401 } 402 403 /** 404 * Updates the specified property on the proxied object 405 * and sends notification of the update to the handler. 406 * 407 * @param name Object containing the name of the property that 408 * should be updated on the proxied object. 409 * 410 * @param value Value that should be set on the proxied object. 411 */ 412 override flash_proxy function setProperty(name:*, value:*):void 413 { 414 var oldVal:* = _item[name]; 415 if (oldVal !== value) 416 { 417 // Update item. 418 _item[name] = value; 419 420 // Stop listening for events on old item if we currently are. 421 var notifier:IPropertyChangeNotifier = 422 IPropertyChangeNotifier(notifiers[name]); 423 if (notifier) 424 { 425 notifier.removeEventListener( 426 PropertyChangeEvent.PROPERTY_CHANGE, 427 propertyChangeHandler); 428 delete notifiers[name]; 429 } 430 431 // Notify anyone interested. 432 if (dispatcher.hasEventListener(PropertyChangeEvent.PROPERTY_CHANGE)) 433 { 434 if (name is QName) 435 name = QName(name).localName; 436 var event:PropertyChangeEvent = 437 PropertyChangeEvent.createUpdateEvent( 438 this, name.toString(), oldVal, value); 439 dispatcher.dispatchEvent(event); 440 } 441 } 442 } 443 444 //-------------------------------------------------------------------------- 445 // 446 // object_proxy methods 447 // 448 //-------------------------------------------------------------------------- 449 450 /** 451 * Provides a place for subclasses to override how a complex property that 452 * needs to be either proxied or daisy chained for event bubbling is managed. 453 * 454 * @param name Typically a string containing the name of the property, 455 * or possibly a QName where the property name is found by 456 * inspecting the <code>localName</code> property. 457 * 458 * @param value The property value. 459 * 460 * @return The property value or an instance of <code>ObjectProxy</code>. 461 */ 462 object_proxy function getComplexProperty(name:*, value:*):* 463 { 464 if (value is IPropertyChangeNotifier) 465 { 466 value.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, 467 propertyChangeHandler); 468 notifiers[name] = value; 469 return value; 470 } 471 472 if (getQualifiedClassName(value) == "Object") 473 { 474 value = new proxyClass(_item[name], null, 475 _proxyLevel > 0 ? _proxyLevel - 1 : _proxyLevel); 476 value.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, 477 propertyChangeHandler); 478 notifiers[name] = value; 479 return value; 480 } 481 482 return value; 483 } 484 485 //-------------------------------------------------------------------------- 486 // 487 // IExternalizable Methods 488 // 489 //-------------------------------------------------------------------------- 490 491 /** 492 * Since Flex only uses ObjectProxy to wrap anonymous objects, 493 * the server flex.messaging.io.ObjectProxy instance serializes itself 494 * as a Map that will be returned as a plain ActionScript object. 495 * You can then set the object_proxy object property to this value. 496 * 497 * @param input The source object from which the ObjectProxy is 498 * deserialized. 499 */ 500 public function readExternal(input:IDataInput):void 501 { 502 var value:Object = input.readObject(); 503 _item = value; 504 } 505 506 /** 507 * Since Flex only serializes the inner ActionScript object that it wraps, 508 * the server flex.messaging.io.ObjectProxy populates itself 509 * with this anonymous object's contents and appears to the user 510 * as a Map. 511 * 512 * @param output The source object from which the ObjectProxy is 513 * deserialized. 514 */ 515 public function writeExternal(output:IDataOutput):void 516 { 517 output.writeObject(_item); 518 } 519 520 //-------------------------------------------------------------------------- 521 // 522 // Methods 523 // 524 //-------------------------------------------------------------------------- 525 526 /** 527 * Registers an event listener object 528 * so that the listener receives notification of an event. 529 * For more information, including descriptions of the parameters see 530 * <code>addEventListener()</code> in the 531 * flash.events.EventDispatcher class. 532 * 533 * @see flash.events.EventDispatcher#addEventListener() 534 */ 535 public function addEventListener(type:String, listener:Function, 536 useCapture:Boolean = false, 537 priority:int = 0, 538 useWeakReference:Boolean = false):void 539 { 540 dispatcher.addEventListener(type, listener, useCapture, 541 priority, useWeakReference); 542 } 543 544 /** 545 * Removes an event listener. 546 * If there is no matching listener registered with the EventDispatcher object, 547 * a call to this method has no effect. 548 * For more information, see 549 * the flash.events.EventDispatcher class. 550 * 551 * @param type The type of event. 552 * 553 * @param listener The listener object to remove. 554 * 555 * @param useCapture Specifies whether the listener was registered for the capture 556 * phase or the target and bubbling phases. If the listener was registered for both 557 * the capture phase and the target and bubbling phases, two calls to 558 * <code>removeEventListener()</code> are required to remove both, one call with 559 * <code>useCapture</code> 560 * set to <code>true</code>, and another call with <code>useCapture</code> 561 * set to <code>false</code>. 562 * 563 * @see flash.events.EventDispatcher#removeEventListener() 564 */ 565 public function removeEventListener(type:String, listener:Function, 566 useCapture:Boolean = false):void 567 { 568 dispatcher.removeEventListener(type, listener, useCapture); 569 } 570 571 /** 572 * Dispatches an event into the event flow. 573 * For more information, see 574 * the flash.events.EventDispatcher class. 575 * 576 * @param event The Event object that is dispatched into the event flow. If the 577 * event is being redispatched, a clone of the event is created automatically. 578 * After an event is dispatched, its target property cannot be changed, so you 579 * must create a new copy of the event for redispatching to work. 580 * 581 * @return Returns <code>true</code> if the event was successfully dispatched. 582 * A value 583 * of <code>false</code> indicates failure or that <code>preventDefault()</code> 584 * was called on the event. 585 * 586 * @see flash.events.EventDispatcher#dispatchEvent() 587 */ 588 public function dispatchEvent(event:Event):Boolean 589 { 590 return dispatcher.dispatchEvent(event); 591 } 592 593 /** 594 * Checks whether there are any event listeners registered 595 * for a specific type of event. 596 * This allows you to determine where an object has altered handling 597 * of an event type in the event flow hierarchy. 598 * For more information, see 599 * the flash.events.EventDispatcher class. 600 * 601 * @param type The type of event 602 * 603 * @return Returns <code>true</code> if a listener of the specified type is 604 * registered; <code>false</code> otherwise. 605 * 606 * @see flash.events.EventDispatcher#hasEventListener() 607 */ 608 public function hasEventListener(type:String):Boolean 609 { 610 return dispatcher.hasEventListener(type); 611 } 612 613 /** 614 * Checks whether an event listener is registered with this object 615 * or any of its ancestors for the specified event type. 616 * This method returns <code>true</code> if an event listener is triggered 617 * during any phase of the event flow when an event of the specified 618 * type is dispatched to this object or any of its descendants. 619 * For more information, see the flash.events.EventDispatcher class. 620 * 621 * @param type The type of event. 622 * 623 * @return Returns <code>true</code> if a listener of the specified type will 624 * be triggered; <code>false</code> otherwise. 625 * 626 * @see flash.events.EventDispatcher#willTrigger() 627 */ 628 public function willTrigger(type:String):Boolean 629 { 630 return dispatcher.willTrigger(type); 631 } 632 633 /** 634 * Called when a complex property is updated. 635 * 636 * @param event An event object that has changed. 637 */ 638 public function propertyChangeHandler(event:PropertyChangeEvent):void 639 { 640 dispatcher.dispatchEvent(event); 641 } 642 643 //-------------------------------------------------------------------------- 644 // 645 // Protected Methods 646 // 647 //-------------------------------------------------------------------------- 648 649 /** 650 * This method creates an array of all of the property names for the 651 * proxied object. 652 * Descendants must override this method if they wish to add more 653 * properties to this list. 654 * Be sure to call <code>super.setupPropertyList</code> before making any 655 * changes to the <code>propertyList</code> property. 656 */ 657 protected function setupPropertyList():void 658 { 659 if (getQualifiedClassName(_item) == "Object") 660 { 661 propertyList = []; 662 for (var prop:String in _item) 663 propertyList.push(prop); 664 } 665 else 666 { 667 propertyList = ObjectUtil.getClassInfo(_item, null, {includeReadOnly:true, uris:["*"]}).properties; 668 } 669 } 670} 671 672} 673