as3signals 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.textile +51 -0
- data/Gemfile +6 -0
- data/MIT-LICENSE.txt +22 -0
- data/README.textile +43 -0
- data/Rakefile +52 -0
- data/as3-signals.as3proj +87 -0
- data/as3-signals.docproj +9 -0
- data/as3signals.gemspec +24 -0
- data/build-asunit.properties +7 -0
- data/build-asunit.xml +34 -0
- data/build.properties +20 -0
- data/build.xml +53 -0
- data/lib/as3signals.rb +15 -0
- data/src/org/osflash/signals/DeluxeSignal.as +260 -0
- data/src/org/osflash/signals/IDeluxeSignal.as +34 -0
- data/src/org/osflash/signals/IDispatcher.as +15 -0
- data/src/org/osflash/signals/ISignal.as +44 -0
- data/src/org/osflash/signals/ISignalOwner.as +13 -0
- data/src/org/osflash/signals/Signal.as +206 -0
- data/src/org/osflash/signals/events/GenericEvent.as +44 -0
- data/src/org/osflash/signals/events/IBubbleEventHandler.as +14 -0
- data/src/org/osflash/signals/events/IEvent.as +27 -0
- data/src/org/osflash/signals/natives/INativeDispatcher.as +34 -0
- data/src/org/osflash/signals/natives/NativeMappedSignal.as +230 -0
- data/src/org/osflash/signals/natives/NativeRelaySignal.as +71 -0
- data/src/org/osflash/signals/natives/NativeSignal.as +176 -0
- data/tests/org/osflash/signals/AllTests.as +44 -0
- data/tests/org/osflash/signals/AllTestsRunner.as +19 -0
- data/tests/org/osflash/signals/AmbiguousRelationshipTest.as +60 -0
- data/tests/org/osflash/signals/DeluxeSignalAmbiguousRelationshipTest.as +60 -0
- data/tests/org/osflash/signals/DeluxeSignalDispatchExtraArgsTest.as +43 -0
- data/tests/org/osflash/signals/DeluxeSignalDispatchNoArgsTest.as +55 -0
- data/tests/org/osflash/signals/DeluxeSignalDispatchNonEventTest.as +67 -0
- data/tests/org/osflash/signals/DeluxeSignalSplitInterfacesTest.as +41 -0
- data/tests/org/osflash/signals/DeluxeSignalTest.as +134 -0
- data/tests/org/osflash/signals/DeluxeSignalWithBubblingEventTest.as +129 -0
- data/tests/org/osflash/signals/DeluxeSignalWithCustomEventTest.as +84 -0
- data/tests/org/osflash/signals/DeluxeSignalWithGenericEventTest.as +190 -0
- data/tests/org/osflash/signals/GenericEventTest.as +62 -0
- data/tests/org/osflash/signals/PriorityListenersTest.as +68 -0
- data/tests/org/osflash/signals/RedispatchedEventTest.as +51 -0
- data/tests/org/osflash/signals/SignalDispatchArgsTest.as +74 -0
- data/tests/org/osflash/signals/SignalDispatchNoArgsTest.as +55 -0
- data/tests/org/osflash/signals/SignalDispatchNonEventTest.as +81 -0
- data/tests/org/osflash/signals/SignalSplitInterfacesTest.as +47 -0
- data/tests/org/osflash/signals/SignalTest.as +267 -0
- data/tests/org/osflash/signals/SignalWithCustomEventTest.as +107 -0
- data/tests/org/osflash/signals/natives/AmbiguousRelationshipInNativeSignalTest.as +63 -0
- data/tests/org/osflash/signals/natives/NativeMappedSignalBoundaryUseTest.as +100 -0
- data/tests/org/osflash/signals/natives/NativeMappedSignalDefaultsTest.as +78 -0
- data/tests/org/osflash/signals/natives/NativeMappedSignalFunctionArgTest.as +148 -0
- data/tests/org/osflash/signals/natives/NativeMappedSignalFunctionNoArgsTest.as +95 -0
- data/tests/org/osflash/signals/natives/NativeMappedSignalObjectArgTest.as +85 -0
- data/tests/org/osflash/signals/natives/NativeRelaySignalTest.as +174 -0
- data/tests/org/osflash/signals/natives/NativeSignalTest.as +312 -0
- metadata +152 -0
@@ -0,0 +1,260 @@
|
|
1
|
+
package org.osflash.signals
|
2
|
+
{
|
3
|
+
import flash.errors.IllegalOperationError;
|
4
|
+
|
5
|
+
import org.osflash.signals.events.IBubbleEventHandler;
|
6
|
+
import org.osflash.signals.events.IEvent;
|
7
|
+
|
8
|
+
/**
|
9
|
+
* Signal dispatches events to multiple listeners.
|
10
|
+
* It is inspired by C# events and delegates, and by
|
11
|
+
* <a target="_top" href="http://en.wikipedia.org/wiki/Signals_and_slots">signals and slots</a>
|
12
|
+
* in Qt.
|
13
|
+
* A Signal adds event dispatching functionality through composition and interfaces,
|
14
|
+
* rather than inheriting from a dispatcher.
|
15
|
+
* <br/><br/>
|
16
|
+
* Project home: <a target="_top" href="http://github.com/robertpenner/as3-signals/">http://github.com/robertpenner/as3-signals/</a>
|
17
|
+
*/
|
18
|
+
public class DeluxeSignal implements IDeluxeSignal, ISignalOwner, IDispatcher
|
19
|
+
{
|
20
|
+
protected var _target:Object;
|
21
|
+
protected var _valueClasses:Array;
|
22
|
+
protected var listenerBoxes:Array;
|
23
|
+
protected var listenersNeedCloning:Boolean = false;
|
24
|
+
|
25
|
+
/**
|
26
|
+
* Creates a DeluxeSignal instance to dispatch events on behalf of a target object.
|
27
|
+
* @param target The object the signal is dispatching events on behalf of.
|
28
|
+
* @param valueClasses Any number of class references that enable type checks in dispatch().
|
29
|
+
* For example, new DeluxeSignal(this, String, uint)
|
30
|
+
* would allow: signal.dispatch("the Answer", 42)
|
31
|
+
* but not: signal.dispatch(true, 42.5)
|
32
|
+
* nor: signal.dispatch()
|
33
|
+
*
|
34
|
+
* NOTE: Subclasses cannot call super.apply(null, valueClasses),
|
35
|
+
* but this constructor has logic to support super(valueClasses).
|
36
|
+
*/
|
37
|
+
public function DeluxeSignal(target:Object, ...valueClasses)
|
38
|
+
{
|
39
|
+
_target = target;
|
40
|
+
listenerBoxes = [];
|
41
|
+
// Cannot use super.apply(null, valueClasses), so allow the subclass to call super(valueClasses).
|
42
|
+
if (valueClasses.length == 1 && valueClasses[0] is Array)
|
43
|
+
valueClasses = valueClasses[0];
|
44
|
+
setValueClasses(valueClasses);
|
45
|
+
}
|
46
|
+
|
47
|
+
/** @inheritDoc */
|
48
|
+
public function get valueClasses():Array { return _valueClasses; }
|
49
|
+
|
50
|
+
/** @inheritDoc */
|
51
|
+
public function get numListeners():uint { return listenerBoxes.length; }
|
52
|
+
|
53
|
+
/** @inheritDoc */
|
54
|
+
public function get target():Object { return _target; }
|
55
|
+
|
56
|
+
/** @inheritDoc */
|
57
|
+
public function set target(value:Object):void
|
58
|
+
{
|
59
|
+
if (value == _target) return;
|
60
|
+
removeAll();
|
61
|
+
_target = value;
|
62
|
+
}
|
63
|
+
|
64
|
+
/** @inheritDoc */
|
65
|
+
//TODO: @throws
|
66
|
+
public function add(listener:Function):Function
|
67
|
+
{
|
68
|
+
return addWithPriority(listener)
|
69
|
+
}
|
70
|
+
|
71
|
+
public function addWithPriority(listener:Function, priority:int = 0):Function
|
72
|
+
{
|
73
|
+
registerListener(listener, false, priority);
|
74
|
+
return listener;
|
75
|
+
}
|
76
|
+
|
77
|
+
public function addOnce(listener:Function):Function
|
78
|
+
{
|
79
|
+
return addOnceWithPriority(listener)
|
80
|
+
}
|
81
|
+
|
82
|
+
/** @inheritDoc */
|
83
|
+
public function addOnceWithPriority(listener:Function, priority:int = 0):Function
|
84
|
+
{
|
85
|
+
registerListener(listener, true, priority);
|
86
|
+
return listener;
|
87
|
+
}
|
88
|
+
|
89
|
+
/** @inheritDoc */
|
90
|
+
public function remove(listener:Function):Function
|
91
|
+
{
|
92
|
+
if (indexOfListener(listener) == -1) return listener;
|
93
|
+
if (listenersNeedCloning)
|
94
|
+
{
|
95
|
+
listenerBoxes = listenerBoxes.slice();
|
96
|
+
listenersNeedCloning = false;
|
97
|
+
}
|
98
|
+
listenerBoxes.splice(indexOfListener(listener), 1);
|
99
|
+
return listener;
|
100
|
+
}
|
101
|
+
|
102
|
+
/** @inheritDoc */
|
103
|
+
public function removeAll():void
|
104
|
+
{
|
105
|
+
// Looping backwards is more efficient when removing array items.
|
106
|
+
for (var i:uint = listenerBoxes.length; i--; )
|
107
|
+
{
|
108
|
+
remove(listenerBoxes[i].listener as Function);
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
/** @inheritDoc */
|
113
|
+
public function dispatch(...valueObjects):void
|
114
|
+
{
|
115
|
+
// Validate value objects against pre-defined value classes.
|
116
|
+
var valueObject:Object;
|
117
|
+
var valueClass:Class;
|
118
|
+
var len:int = _valueClasses.length;
|
119
|
+
for (var i:int = 0; i < len; i++)
|
120
|
+
{
|
121
|
+
// null is allowed to pass through.
|
122
|
+
if ( (valueObject = valueObjects[i]) === null
|
123
|
+
|| valueObject is (valueClass = _valueClasses[i]) )
|
124
|
+
continue;
|
125
|
+
|
126
|
+
throw new ArgumentError('Value object <' + valueObject
|
127
|
+
+ '> is not an instance of <' + valueClass + '>.');
|
128
|
+
}
|
129
|
+
|
130
|
+
var event:IEvent = valueObjects[0] as IEvent;
|
131
|
+
if (event)
|
132
|
+
{
|
133
|
+
// clone re-dispatched event
|
134
|
+
if (event.target)
|
135
|
+
{
|
136
|
+
valueObjects[0] = event = event.clone();
|
137
|
+
}
|
138
|
+
event.target = this.target;
|
139
|
+
event.currentTarget = this.target;
|
140
|
+
event.signal = this;
|
141
|
+
}
|
142
|
+
|
143
|
+
// During a dispatch, add() and remove() should clone listeners array instead of modifying it.
|
144
|
+
listenersNeedCloning = true;
|
145
|
+
//// Call listeners.
|
146
|
+
var listener:Function;
|
147
|
+
if (listenerBoxes.length)
|
148
|
+
{
|
149
|
+
//TODO: investigate performance of various approaches
|
150
|
+
|
151
|
+
for each (var listenerBox:Object in listenerBoxes)
|
152
|
+
{
|
153
|
+
listener = listenerBox.listener;
|
154
|
+
if (listenerBox.once) remove(listener);
|
155
|
+
listener.apply(null, valueObjects);
|
156
|
+
}
|
157
|
+
}
|
158
|
+
|
159
|
+
listenersNeedCloning = false;
|
160
|
+
|
161
|
+
if (!event || !event.bubbles) return;
|
162
|
+
|
163
|
+
//// Bubble the event as far as possible.
|
164
|
+
var currentTarget:Object = this.target;
|
165
|
+
while ( currentTarget && currentTarget.hasOwnProperty("parent")
|
166
|
+
&& (currentTarget = currentTarget.parent) )
|
167
|
+
{
|
168
|
+
if (currentTarget is IBubbleEventHandler)
|
169
|
+
{
|
170
|
+
// onEventBubbled() can stop the bubbling by returning false.
|
171
|
+
if (!IBubbleEventHandler(event.currentTarget = currentTarget).onEventBubbled(event))
|
172
|
+
break;
|
173
|
+
}
|
174
|
+
}
|
175
|
+
}
|
176
|
+
|
177
|
+
protected function indexOfListener(listener:Function):int
|
178
|
+
{
|
179
|
+
for (var i:int = listenerBoxes.length; i--; )
|
180
|
+
{
|
181
|
+
if (listenerBoxes[i].listener == listener) return i;
|
182
|
+
}
|
183
|
+
return -1;
|
184
|
+
}
|
185
|
+
|
186
|
+
protected function setValueClasses(valueClasses:Array):void
|
187
|
+
{
|
188
|
+
_valueClasses = valueClasses || [];
|
189
|
+
|
190
|
+
for (var i:int = _valueClasses.length; i--; )
|
191
|
+
{
|
192
|
+
if (!(_valueClasses[i] is Class))
|
193
|
+
{
|
194
|
+
throw new ArgumentError('Invalid valueClasses argument: item at index ' + i
|
195
|
+
+ ' should be a Class but was:<' + _valueClasses[i] + '>.');
|
196
|
+
}
|
197
|
+
}
|
198
|
+
}
|
199
|
+
|
200
|
+
protected function registerListener(listener:Function, once:Boolean = false, priority:int = 0):void
|
201
|
+
{
|
202
|
+
// function.length is the number of arguments.
|
203
|
+
if (listener.length < _valueClasses.length)
|
204
|
+
{
|
205
|
+
var argumentString:String = (listener.length == 1) ? 'argument' : 'arguments';
|
206
|
+
throw new ArgumentError('Listener has '+listener.length+' '+argumentString+' but it needs at least '+_valueClasses.length+' to match the given value classes.');
|
207
|
+
}
|
208
|
+
|
209
|
+
var listenerBox:Object = { listener:listener, once:once, priority:priority };
|
210
|
+
// Process the first listener as quickly as possible.
|
211
|
+
if (!listenerBoxes.length)
|
212
|
+
{
|
213
|
+
listenerBoxes[0] = listenerBox;
|
214
|
+
return;
|
215
|
+
}
|
216
|
+
|
217
|
+
var prevListenerIndex:int = indexOfListener(listener);
|
218
|
+
if (prevListenerIndex >= 0)
|
219
|
+
{
|
220
|
+
// If the listener was previously added, definitely don't add it again.
|
221
|
+
// But throw an exception in some cases, as the error messages explain.
|
222
|
+
var prevListenerBox:Object = listenerBoxes[prevListenerIndex];
|
223
|
+
if (prevListenerBox.once && !once)
|
224
|
+
{
|
225
|
+
throw new IllegalOperationError('You cannot addOnce() then add() the same listener without removing the relationship first.');
|
226
|
+
}
|
227
|
+
else if (!prevListenerBox.once && once)
|
228
|
+
{
|
229
|
+
throw new IllegalOperationError('You cannot add() then addOnce() the same listener without removing the relationship first.');
|
230
|
+
}
|
231
|
+
// Listener was already added, so do nothing.
|
232
|
+
return;
|
233
|
+
}
|
234
|
+
|
235
|
+
if (listenersNeedCloning)
|
236
|
+
{
|
237
|
+
listenerBoxes = listenerBoxes.slice();
|
238
|
+
listenersNeedCloning = false;
|
239
|
+
}
|
240
|
+
|
241
|
+
// Assume the listeners are already sorted by priority
|
242
|
+
// and insert in the right spot. For listeners with the same priority,
|
243
|
+
// we must preserve the order in which they were added.
|
244
|
+
var len:int = listenerBoxes.length;
|
245
|
+
for (var i:int = 0; i < len; i++)
|
246
|
+
{
|
247
|
+
// As soon as a lower-priority listener is found, go in front of it.
|
248
|
+
if (priority > listenerBoxes[i].priority)
|
249
|
+
{
|
250
|
+
listenerBoxes.splice(i, 0, listenerBox);
|
251
|
+
return;
|
252
|
+
}
|
253
|
+
}
|
254
|
+
|
255
|
+
// If we made it this far, the new listener has lowest priority, so put it last.
|
256
|
+
listenerBoxes[listenerBoxes.length] = listenerBox;
|
257
|
+
}
|
258
|
+
|
259
|
+
}
|
260
|
+
}
|
@@ -0,0 +1,34 @@
|
|
1
|
+
package org.osflash.signals
|
2
|
+
{
|
3
|
+
/**
|
4
|
+
*
|
5
|
+
*/
|
6
|
+
public interface IDeluxeSignal extends ISignal
|
7
|
+
{
|
8
|
+
/**
|
9
|
+
* Subscribes a listener for the signal.
|
10
|
+
* After you successfully register an event listener,
|
11
|
+
* you cannot change its priority through additional calls to add().
|
12
|
+
* To change a listener's priority, you must first call remove().
|
13
|
+
* Then you can register the listener again with the new priority level.
|
14
|
+
* @param listener A function with an argument
|
15
|
+
* that matches the type of event dispatched by the signal.
|
16
|
+
* If eventClass is not specified, the listener and dispatch() can be called without an argument.
|
17
|
+
*/
|
18
|
+
function addWithPriority(listener:Function, priority:int = 0):Function
|
19
|
+
|
20
|
+
/**
|
21
|
+
* Subscribes a one-time listener for this signal.
|
22
|
+
* The signal will remove the listener automatically the first time it is called,
|
23
|
+
* after the dispatch to all listeners is complete.
|
24
|
+
* @param listener A function with an argument
|
25
|
+
* that matches the type of event dispatched by the signal.
|
26
|
+
* If eventClass is not specified, the listener and dispatch() can be called without an argument.
|
27
|
+
* @param priority The priority level of the event listener.
|
28
|
+
* The priority is designated by a signed 32-bit integer.
|
29
|
+
* The higher the number, the higher the priority.
|
30
|
+
* All listeners with priority n are processed before listeners of priority n-1.
|
31
|
+
*/
|
32
|
+
function addOnceWithPriority(listener:Function, priority:int = 0):Function
|
33
|
+
}
|
34
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
package org.osflash.signals
|
2
|
+
{
|
3
|
+
/**
|
4
|
+
*
|
5
|
+
*/
|
6
|
+
public interface IDispatcher
|
7
|
+
{
|
8
|
+
/**
|
9
|
+
* Dispatches an object to listeners.
|
10
|
+
* @param valueObjects Any number of parameters to send to listeners. Will be type-checked against valueClasses.
|
11
|
+
* @throws ArgumentError <code>ArgumentError</code>: valueObjects are not compatible with valueClasses.
|
12
|
+
*/
|
13
|
+
function dispatch(...valueObjects):void;
|
14
|
+
}
|
15
|
+
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
package org.osflash.signals
|
2
|
+
{
|
3
|
+
/**
|
4
|
+
*
|
5
|
+
*/
|
6
|
+
public interface ISignal
|
7
|
+
{
|
8
|
+
/**
|
9
|
+
* An optional array of classes defining the types of parameters sent to listeners.
|
10
|
+
*/
|
11
|
+
function get valueClasses():Array;
|
12
|
+
|
13
|
+
/** The current number of listeners for the signal. */
|
14
|
+
function get numListeners():uint;
|
15
|
+
|
16
|
+
/**
|
17
|
+
* Subscribes a listener for the signal.
|
18
|
+
* @param listener A function with arguments
|
19
|
+
* that matches the value classes dispatched by the signal.
|
20
|
+
* If value classes are not specified (e.g. via Signal constructor), dispatch() can be called without arguments.
|
21
|
+
* @return the listener Function passed as the parameter
|
22
|
+
*/
|
23
|
+
function add(listener:Function):Function;
|
24
|
+
|
25
|
+
/**
|
26
|
+
* Subscribes a one-time listener for this signal.
|
27
|
+
* The signal will remove the listener automatically the first time it is called,
|
28
|
+
* after the dispatch to all listeners is complete.
|
29
|
+
* @param listener A function with arguments
|
30
|
+
* that matches the value classes dispatched by the signal.
|
31
|
+
* If value classes are not specified (e.g. via Signal constructor), dispatch() can be called without arguments.
|
32
|
+
* @return the listener Function passed as the parameter
|
33
|
+
*/
|
34
|
+
function addOnce(listener:Function):Function;
|
35
|
+
|
36
|
+
/**
|
37
|
+
* Unsubscribes a listener from the signal.
|
38
|
+
* @param listener
|
39
|
+
* @return the listener Function passed as the parameter
|
40
|
+
*/
|
41
|
+
function remove(listener:Function):Function;
|
42
|
+
|
43
|
+
}
|
44
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
package org.osflash.signals
|
2
|
+
{
|
3
|
+
/**
|
4
|
+
* ISignalOwner gives access to the powerful function, removeAll. This should only be called by trusted classes.
|
5
|
+
*/
|
6
|
+
public interface ISignalOwner extends ISignal
|
7
|
+
{
|
8
|
+
/**
|
9
|
+
* Unsubscribes all listeners from the signal.
|
10
|
+
*/
|
11
|
+
function removeAll():void
|
12
|
+
}
|
13
|
+
}
|
@@ -0,0 +1,206 @@
|
|
1
|
+
package org.osflash.signals
|
2
|
+
{
|
3
|
+
import flash.errors.IllegalOperationError;
|
4
|
+
import flash.utils.Dictionary;
|
5
|
+
|
6
|
+
/**
|
7
|
+
* Signal dispatches events to multiple listeners.
|
8
|
+
* It is inspired by C# events and delegates, and by
|
9
|
+
* <a target="_top" href="http://en.wikipedia.org/wiki/Signals_and_slots">signals and slots</a>
|
10
|
+
* in Qt.
|
11
|
+
* A Signal adds event dispatching functionality through composition and interfaces,
|
12
|
+
* rather than inheriting from a dispatcher.
|
13
|
+
* <br/><br/>
|
14
|
+
* Project home: <a target="_top" href="http://github.com/robertpenner/as3-signals/">http://github.com/robertpenner/as3-signals/</a>
|
15
|
+
*/
|
16
|
+
public class Signal implements ISignalOwner, IDispatcher
|
17
|
+
{
|
18
|
+
protected var _valueClasses:Array; // of Class
|
19
|
+
protected var listeners:Array; // of Function
|
20
|
+
protected var onceListeners:Dictionary; // of Function
|
21
|
+
protected var listenersNeedCloning:Boolean = false;
|
22
|
+
|
23
|
+
/**
|
24
|
+
* Creates a Signal instance to dispatch value objects.
|
25
|
+
* @param valueClasses Any number of class references that enable type checks in dispatch().
|
26
|
+
* For example, new Signal(String, uint)
|
27
|
+
* would allow: signal.dispatch("the Answer", 42)
|
28
|
+
* but not: signal.dispatch(true, 42.5)
|
29
|
+
* nor: signal.dispatch()
|
30
|
+
*
|
31
|
+
* NOTE: Subclasses cannot call super.apply(null, valueClasses),
|
32
|
+
* but this constructor has logic to support super(valueClasses).
|
33
|
+
*/
|
34
|
+
public function Signal(...valueClasses)
|
35
|
+
{
|
36
|
+
listeners = [];
|
37
|
+
onceListeners = new Dictionary();
|
38
|
+
// Cannot use super.apply(null, valueClasses), so allow the subclass to call super(valueClasses).
|
39
|
+
if (valueClasses.length == 1 && valueClasses[0] is Array)
|
40
|
+
valueClasses = valueClasses[0];
|
41
|
+
setValueClasses(valueClasses);
|
42
|
+
}
|
43
|
+
|
44
|
+
/** @inheritDoc */
|
45
|
+
public function get valueClasses():Array { return _valueClasses; }
|
46
|
+
|
47
|
+
/** @inheritDoc */
|
48
|
+
public function get numListeners():uint { return listeners.length; }
|
49
|
+
|
50
|
+
/** @inheritDoc */
|
51
|
+
//TODO: @throws
|
52
|
+
public function add(listener:Function):Function
|
53
|
+
{
|
54
|
+
registerListener(listener);
|
55
|
+
return listener;
|
56
|
+
}
|
57
|
+
|
58
|
+
/** @inheritDoc */
|
59
|
+
public function addOnce(listener:Function):Function
|
60
|
+
{
|
61
|
+
registerListener(listener, true);
|
62
|
+
return listener;
|
63
|
+
}
|
64
|
+
|
65
|
+
/** @inheritDoc */
|
66
|
+
public function remove(listener:Function):Function
|
67
|
+
{
|
68
|
+
var index:int = listeners.indexOf(listener);
|
69
|
+
if (index == -1) return listener;
|
70
|
+
if (listenersNeedCloning)
|
71
|
+
{
|
72
|
+
listeners = listeners.slice();
|
73
|
+
listenersNeedCloning = false;
|
74
|
+
}
|
75
|
+
listeners.splice(index, 1);
|
76
|
+
delete onceListeners[listener];
|
77
|
+
return listener;
|
78
|
+
}
|
79
|
+
|
80
|
+
/** @inheritDoc */
|
81
|
+
public function removeAll():void
|
82
|
+
{
|
83
|
+
// Looping backwards is more efficient when removing array items.
|
84
|
+
for (var i:uint = listeners.length; i--; )
|
85
|
+
{
|
86
|
+
remove(listeners[i] as Function);
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
/** @inheritDoc */
|
91
|
+
public function dispatch(...valueObjects):void
|
92
|
+
{
|
93
|
+
// Validate value objects against pre-defined value classes.
|
94
|
+
var valueObject:Object;
|
95
|
+
var valueClass:Class;
|
96
|
+
var numValueClasses:int = _valueClasses.length;
|
97
|
+
if (valueObjects.length < numValueClasses)
|
98
|
+
{
|
99
|
+
throw new ArgumentError('Incorrect number of arguments. Expected at least ' + numValueClasses + ' but received ' + valueObjects.length + '.');
|
100
|
+
}
|
101
|
+
|
102
|
+
for (var i:int = 0; i < numValueClasses; i++)
|
103
|
+
{
|
104
|
+
// null is allowed to pass through.
|
105
|
+
if ( (valueObject = valueObjects[i]) === null
|
106
|
+
|| valueObject is (valueClass = _valueClasses[i]) )
|
107
|
+
continue;
|
108
|
+
|
109
|
+
throw new ArgumentError('Value object <' + valueObject
|
110
|
+
+ '> is not an instance of <' + valueClass + '>.');
|
111
|
+
}
|
112
|
+
|
113
|
+
if (!listeners.length) return;
|
114
|
+
|
115
|
+
//// Call listeners.
|
116
|
+
|
117
|
+
// During a dispatch, add() and remove() should clone listeners array instead of modifying it.
|
118
|
+
listenersNeedCloning = true;
|
119
|
+
var listener:Function;
|
120
|
+
switch (valueObjects.length)
|
121
|
+
{
|
122
|
+
case 0:
|
123
|
+
for each (listener in listeners)
|
124
|
+
{
|
125
|
+
if (onceListeners[listener]) remove(listener);
|
126
|
+
listener();
|
127
|
+
}
|
128
|
+
break;
|
129
|
+
|
130
|
+
case 1:
|
131
|
+
for each (listener in listeners)
|
132
|
+
{
|
133
|
+
if (onceListeners[listener]) remove(listener);
|
134
|
+
listener(valueObjects[0]);
|
135
|
+
}
|
136
|
+
break;
|
137
|
+
|
138
|
+
default:
|
139
|
+
for each (listener in listeners)
|
140
|
+
{
|
141
|
+
if (onceListeners[listener]) remove(listener);
|
142
|
+
listener.apply(null, valueObjects);
|
143
|
+
}
|
144
|
+
}
|
145
|
+
listenersNeedCloning = false;
|
146
|
+
}
|
147
|
+
|
148
|
+
protected function setValueClasses(valueClasses:Array):void
|
149
|
+
{
|
150
|
+
_valueClasses = valueClasses || [];
|
151
|
+
|
152
|
+
for (var i:int = _valueClasses.length; i--; )
|
153
|
+
{
|
154
|
+
if (!(_valueClasses[i] is Class))
|
155
|
+
{
|
156
|
+
throw new ArgumentError('Invalid valueClasses argument: item at index ' + i
|
157
|
+
+ ' should be a Class but was:<' + _valueClasses[i] + '>.');
|
158
|
+
}
|
159
|
+
}
|
160
|
+
}
|
161
|
+
|
162
|
+
protected function registerListener(listener:Function, once:Boolean = false):void
|
163
|
+
{
|
164
|
+
// function.length is the number of arguments.
|
165
|
+
if (listener.length < _valueClasses.length)
|
166
|
+
{
|
167
|
+
var argumentString:String = (listener.length == 1) ? 'argument' : 'arguments';
|
168
|
+
throw new ArgumentError('Listener has '+listener.length+' '+argumentString+' but it needs at least '+_valueClasses.length+' to match the given value classes.');
|
169
|
+
}
|
170
|
+
|
171
|
+
// If there are no previous listeners, add the first one as quickly as possible.
|
172
|
+
if (!listeners.length)
|
173
|
+
{
|
174
|
+
listeners[0] = listener;
|
175
|
+
if (once) onceListeners[listener] = true;
|
176
|
+
return;
|
177
|
+
}
|
178
|
+
|
179
|
+
if (listeners.indexOf(listener) >= 0)
|
180
|
+
{
|
181
|
+
// If the listener was previously added, definitely don't add it again.
|
182
|
+
// But throw an exception in some cases, as the error messages explain.
|
183
|
+
if (onceListeners[listener] && !once)
|
184
|
+
{
|
185
|
+
throw new IllegalOperationError('You cannot addOnce() then add() the same listener without removing the relationship first.');
|
186
|
+
}
|
187
|
+
else if (!onceListeners[listener] && once)
|
188
|
+
{
|
189
|
+
throw new IllegalOperationError('You cannot add() then addOnce() the same listener without removing the relationship first.');
|
190
|
+
}
|
191
|
+
// Listener was already added, so do nothing.
|
192
|
+
return;
|
193
|
+
}
|
194
|
+
|
195
|
+
if (listenersNeedCloning)
|
196
|
+
{
|
197
|
+
listeners = listeners.slice();
|
198
|
+
listenersNeedCloning = false;
|
199
|
+
}
|
200
|
+
|
201
|
+
// Faster than push().
|
202
|
+
listeners[listeners.length] = listener;
|
203
|
+
if (once) onceListeners[listener] = true;
|
204
|
+
}
|
205
|
+
}
|
206
|
+
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
package org.osflash.signals.events
|
2
|
+
{
|
3
|
+
import org.osflash.signals.IDeluxeSignal;
|
4
|
+
|
5
|
+
/**
|
6
|
+
*
|
7
|
+
* @see org.osflash.signals.events.IEvent
|
8
|
+
* Documentation for the event interface being maintained in IEvent to avoid duplication for now.
|
9
|
+
*/
|
10
|
+
public class GenericEvent implements IEvent
|
11
|
+
{
|
12
|
+
protected var _bubbles:Boolean;
|
13
|
+
protected var _target:Object;
|
14
|
+
protected var _currentTarget:Object;
|
15
|
+
protected var _signal:IDeluxeSignal;
|
16
|
+
|
17
|
+
public function GenericEvent(bubbles:Boolean = false)
|
18
|
+
{
|
19
|
+
_bubbles = bubbles;
|
20
|
+
}
|
21
|
+
|
22
|
+
/** @inheritDoc */
|
23
|
+
public function get signal():IDeluxeSignal { return _signal; }
|
24
|
+
public function set signal(value:IDeluxeSignal):void { _signal = value; }
|
25
|
+
|
26
|
+
/** @inheritDoc */
|
27
|
+
public function get target():Object { return _target; }
|
28
|
+
public function set target(value:Object):void { _target = value; }
|
29
|
+
|
30
|
+
/** @inheritDoc */
|
31
|
+
public function get currentTarget():Object { return _currentTarget; }
|
32
|
+
public function set currentTarget(value:Object):void { _currentTarget = value; }
|
33
|
+
|
34
|
+
/** @inheritDoc */
|
35
|
+
public function get bubbles():Boolean { return _bubbles; }
|
36
|
+
public function set bubbles(value:Boolean):void { _bubbles = value; }
|
37
|
+
|
38
|
+
/** @inheritDoc */
|
39
|
+
public function clone():IEvent
|
40
|
+
{
|
41
|
+
return new GenericEvent(_bubbles);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
package org.osflash.signals.events
|
2
|
+
{
|
3
|
+
|
4
|
+
public interface IBubbleEventHandler
|
5
|
+
{
|
6
|
+
/**
|
7
|
+
* Handler for event bubbling.
|
8
|
+
* It's left to the IBubbleEventHandler to decide what to do with the event.
|
9
|
+
* @param event The event that bubbled up.
|
10
|
+
* @return whether to continue bubbling this event
|
11
|
+
*/
|
12
|
+
function onEventBubbled(event:IEvent):Boolean;
|
13
|
+
}
|
14
|
+
}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
package org.osflash.signals.events
|
2
|
+
{
|
3
|
+
import org.osflash.signals.IDeluxeSignal;
|
4
|
+
|
5
|
+
public interface IEvent
|
6
|
+
{
|
7
|
+
/** The object that originally dispatched the event.
|
8
|
+
* When dispatched from an signal, the target is the object containing the signal. */
|
9
|
+
function get target():Object;
|
10
|
+
function set target(value:Object):void;
|
11
|
+
|
12
|
+
/** The object that added the listener for the event. */
|
13
|
+
function get currentTarget():Object;
|
14
|
+
function set currentTarget(value:Object):void;
|
15
|
+
|
16
|
+
/** The signal that dispatched the event. */
|
17
|
+
function get signal():IDeluxeSignal;
|
18
|
+
function set signal(value:IDeluxeSignal):void;
|
19
|
+
|
20
|
+
/** Indicates whether the event is a bubbling event. */
|
21
|
+
function get bubbles():Boolean;
|
22
|
+
function set bubbles(value:Boolean):void;
|
23
|
+
|
24
|
+
/** Returns a new copy of the instance. */
|
25
|
+
function clone():IEvent;
|
26
|
+
}
|
27
|
+
}
|
@@ -0,0 +1,34 @@
|
|
1
|
+
package org.osflash.signals.natives
|
2
|
+
{
|
3
|
+
import flash.events.Event;
|
4
|
+
import flash.events.IEventDispatcher;
|
5
|
+
|
6
|
+
/**
|
7
|
+
* Similar to IDispatcher but using strong types specific to Flash's native event system.
|
8
|
+
*/
|
9
|
+
public interface INativeDispatcher
|
10
|
+
{
|
11
|
+
/**
|
12
|
+
* The type of event permitted to be dispatched. Corresponds to flash.events.Event.type.
|
13
|
+
*/
|
14
|
+
function get eventType():String;
|
15
|
+
|
16
|
+
/**
|
17
|
+
* The class of event permitted to be dispatched. Will be flash.events.Event or a subclass.
|
18
|
+
*/
|
19
|
+
function get eventClass():Class;
|
20
|
+
|
21
|
+
/**
|
22
|
+
* The object considered the source of the dispatched events.
|
23
|
+
*/
|
24
|
+
function get target():IEventDispatcher;
|
25
|
+
|
26
|
+
/**
|
27
|
+
* Dispatches an event to listeners.
|
28
|
+
* @param event An instance of a class that is or extends flash.events.Event.
|
29
|
+
* @throws ArgumentError <code>ArgumentError</code>: Event object [event] is not an instance of [eventClass].
|
30
|
+
* @throws ArgumentError <code>ArgumentError</code>: Event object has incorrect type. Expected [eventType] but was [event.type].
|
31
|
+
*/
|
32
|
+
function dispatch(event:Event):Boolean;
|
33
|
+
}
|
34
|
+
}
|