PageRenderTime 42ms CodeModel.GetById 15ms app.highlight 19ms RepoModel.GetById 2ms app.codeStats 0ms

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