/src/org/osflash/signals/natives/NativeSignal.as

http://github.com/robertpenner/as3-signals · ActionScript · 227 lines · 123 code · 34 blank · 70 comment · 22 complexity · 8ca6c3f1f70ab0f315aa44b1b6c94fa6 MD5 · raw file

  1. package org.osflash.signals.natives
  2. {
  3. import org.osflash.signals.ISlot;
  4. import org.osflash.signals.Slot;
  5. import org.osflash.signals.SlotList;
  6. import flash.errors.IllegalOperationError;
  7. import flash.events.Event;
  8. import flash.events.IEventDispatcher;
  9. /**
  10. * Allows the eventClass to be set in MXML, e.g.
  11. * <natives:NativeSignal id="clicked" eventType="click" target="{this}">{MouseEvent}</natives:NativeSignal>
  12. */
  13. [DefaultProperty("eventClass")]
  14. /**
  15. * The NativeSignal class provides a strongly-typed facade for an IEventDispatcher.
  16. * A NativeSignal is essentially a mini-dispatcher locked to a specific event type and class.
  17. * It can become part of an interface.
  18. */
  19. public class NativeSignal implements INativeDispatcher
  20. {
  21. protected var _target:IEventDispatcher;
  22. protected var _eventType:String;
  23. protected var _eventClass:Class;
  24. protected var _valueClasses:Array;
  25. protected var slots:SlotList;
  26. /**
  27. * Creates a NativeSignal instance to dispatch events on behalf of a target object.
  28. * @param target The object on whose behalf the signal is dispatching events.
  29. * @param eventType The type of Event permitted to be dispatched from this signal. Corresponds to Event.type.
  30. * @param eventClass An optional class reference that enables an event type check in dispatch(). Defaults to flash.events.Event if omitted.
  31. */
  32. public function NativeSignal(target:IEventDispatcher = null, eventType:String = "", eventClass:Class = null)
  33. {
  34. slots = SlotList.NIL;
  35. this.target = target;
  36. this.eventType = eventType;
  37. this.eventClass = eventClass;
  38. }
  39. /** @inheritDoc */
  40. public function get eventType():String { return _eventType; }
  41. public function set eventType(value:String):void { _eventType = value; }
  42. /** @inheritDoc */
  43. public function get eventClass():Class { return _eventClass; }
  44. public function set eventClass(value:Class):void
  45. {
  46. _eventClass = value || Event;
  47. _valueClasses = [_eventClass];
  48. }
  49. /** @inheritDoc */
  50. [ArrayElementType("Class")]
  51. public function get valueClasses():Array { return _valueClasses; }
  52. public function set valueClasses(value:Array):void
  53. {
  54. eventClass = value && value.length > 0 ? value[0] : null;
  55. }
  56. /** @inheritDoc */
  57. public function get numListeners():uint { return slots.length; }
  58. /** @inheritDoc */
  59. public function get target():IEventDispatcher { return _target; }
  60. public function set target(value:IEventDispatcher):void
  61. {
  62. if (value == _target) return;
  63. if (_target) removeAll();
  64. _target = value;
  65. }
  66. /**
  67. * @inheritDoc
  68. * @throws flash.errors.IllegalOperationError <code>IllegalOperationError</code>: You cannot addOnce() then add() the same listener without removing the relationship first.
  69. * @throws ArgumentError <code>ArgumentError</code>: Given listener is <code>null</code>.
  70. * @throws ArgumentError <code>ArgumentError</code>: Target object cannot be <code>null</code>.
  71. */
  72. public function add(listener:Function):ISlot
  73. {
  74. return addWithPriority(listener);
  75. }
  76. /**
  77. * @inheritDoc
  78. * @throws flash.errors.IllegalOperationError <code>IllegalOperationError</code>: You cannot addOnce() then add() the same listener without removing the relationship first.
  79. * @throws ArgumentError <code>ArgumentError</code>: Given listener is <code>null</code>.
  80. * @throws ArgumentError <code>ArgumentError</code>: Target object cannot be <code>null</code>.
  81. */
  82. public function addWithPriority(listener:Function, priority:int = 0):ISlot
  83. {
  84. return registerListenerWithPriority(listener, false, priority);
  85. }
  86. /**
  87. * @inheritDoc
  88. * @throws flash.errors.IllegalOperationError <code>IllegalOperationError</code>: You cannot addOnce() then add() the same listener without removing the relationship first.
  89. * @throws ArgumentError <code>ArgumentError</code>: Given listener is <code>null</code>.
  90. * @throws ArgumentError <code>ArgumentError</code>: Target object cannot be <code>null</code>.
  91. */
  92. public function addOnce(listener:Function):ISlot
  93. {
  94. return addOnceWithPriority(listener);
  95. }
  96. /**
  97. * @inheritDoc
  98. * @throws flash.errors.IllegalOperationError <code>IllegalOperationError</code>: You cannot addOnce() then add() the same listener without removing the relationship first.
  99. * @throws ArgumentError <code>ArgumentError</code>: Given listener is <code>null</code>.
  100. * @throws ArgumentError <code>ArgumentError</code>: Target object cannot be <code>null</code>.
  101. */
  102. public function addOnceWithPriority(listener:Function, priority:int = 0):ISlot
  103. {
  104. return registerListenerWithPriority(listener, true, priority);
  105. }
  106. /** @inheritDoc */
  107. public function remove(listener:Function):ISlot
  108. {
  109. const slot:ISlot = slots.find(listener);
  110. if (!slot) return null;
  111. _target.removeEventListener(_eventType, slot.execute1);
  112. slots = slots.filterNot(listener);
  113. return slot;
  114. }
  115. /** @inheritDoc */
  116. public function removeAll():void
  117. {
  118. var slotsToProcess:SlotList = slots;
  119. while (slotsToProcess.nonEmpty)
  120. {
  121. target.removeEventListener(_eventType, slotsToProcess.head.execute1);
  122. slotsToProcess = slotsToProcess.tail;
  123. }
  124. slots = SlotList.NIL;
  125. }
  126. /**
  127. * @inheritDoc
  128. * @throws ArgumentError <code>ArgumentError</code>: Event object expected.
  129. * @throws ArgumentError <code>ArgumentError</code>: No more than one Event object expected.
  130. * @throws ArgumentError <code>ArgumentError</code>: Target object cannot be <code>null</code>.
  131. * @throws ArgumentError <code>ArgumentError</code>: Event object cannot be <code>null</code>.
  132. * @throws ArgumentError <code>ArgumentError</code>: Event object [event] is not an instance of [eventClass].
  133. * @throws ArgumentError <code>ArgumentError</code>: Event object has incorrect type. Expected [eventType] but was [event.type].
  134. */
  135. public function dispatch(...valueObjects):void
  136. {
  137. //TODO: check if ...valueObjects can ever be null.
  138. if (null == valueObjects) throw new ArgumentError('Event object expected.');
  139. if (valueObjects.length != 1) throw new ArgumentError('No more than one Event object expected.');
  140. dispatchEvent(valueObjects[0] as Event);
  141. }
  142. /**
  143. * Unlike other signals, NativeSignal does not dispatch null
  144. * because it causes an exception in EventDispatcher.
  145. * @inheritDoc
  146. * @throws ArgumentError <code>ArgumentError</code>: Target object cannot be <code>null</code>.
  147. * @throws ArgumentError <code>ArgumentError</code>: Event object cannot be <code>null</code>.
  148. * @throws ArgumentError <code>ArgumentError</code>: Event object [event] is not an instance of [eventClass].
  149. * @throws ArgumentError <code>ArgumentError</code>: Event object has incorrect type. Expected [eventType] but was [event.type].
  150. */
  151. public function dispatchEvent(event:Event):Boolean
  152. {
  153. if (!target) throw new ArgumentError('Target object cannot be null.');
  154. if (!event) throw new ArgumentError('Event object cannot be null.');
  155. if (!(event is eventClass))
  156. throw new ArgumentError('Event object '+event+' is not an instance of '+eventClass+'.');
  157. if (event.type != eventType)
  158. throw new ArgumentError('Event object has incorrect type. Expected <'+eventType+'> but was <'+event.type+'>.');
  159. return target.dispatchEvent(event);
  160. }
  161. protected function registerListenerWithPriority(listener:Function, once:Boolean = false, priority:int = 0):ISlot
  162. {
  163. if (!target) throw new ArgumentError('Target object cannot be null.');
  164. if (registrationPossible(listener, once))
  165. {
  166. const slot:ISlot = new Slot(listener, this, once, priority);
  167. // Not necessary to insertWithPriority() because the target takes care of ordering.
  168. slots = slots.prepend(slot);
  169. _target.addEventListener(_eventType, slot.execute1, false, priority);
  170. return slot;
  171. }
  172. return slots.find(listener);
  173. }
  174. protected function registrationPossible(listener:Function, once:Boolean):Boolean
  175. {
  176. if (!slots.nonEmpty) return true;
  177. const existingSlot:ISlot = slots.find(listener);
  178. if (existingSlot)
  179. {
  180. if (existingSlot.once != once)
  181. {
  182. // If the listener was previously added, definitely don't add it again.
  183. // But throw an exception if their once value differs.
  184. throw new IllegalOperationError('You cannot addOnce() then add() the same listener without removing the relationship first.');
  185. }
  186. // Listener was already added.
  187. return false;
  188. }
  189. // This listener has not been added before.
  190. return true;
  191. }
  192. }
  193. }