I was wondering what the appropriate form was when creating custom events? Should one create a CustomEvent class, and then create a temporary dispatcher in the function, and dispatch the CustomEvent. or is it better to attempt to create a CustomEventDispatcher class, and create the CustomEvent class as an internal class of that class, eg:
package
{
public class CustomEventDispatcher extends EventDispatcher
{
public function CustomEventDispatcher()
{
super(new CustomEvent());
}
}
}
class CustomEvent
{
public function CustomEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false)
{
super(type, bubbles, cancelable)
}
}
Unilaterally, it is better to make events publicly accessible. This way you can type your listeners (good for code hinting and debugging) and have the Event have public static const types (which you also may want to look in to).
There are two basic questions to answer, when conceiving event mechanics.
1) How do I create dispatcher instance for my events?
General options are: extend EventDispatcher, or aggregate dispatcher instance.
Most basic and common practice (and official docs also state that), is extending EventDispatcher class, thus giving your classes event-dispatching capabilities.
Pros: simple to implement -- just type extends EventDispatcher, and you are done.
Cons: you can't extend something else. Apparently, this is the reason why many native classes are EventDispatcher's grandchildren. Just to spare us the trouble, I guess.
Second general approach is aggregating a dispatcher instance.
package
{
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
public class ClassA implements IEventDispatcher
{
private var dispatcher:EventDispatcher;
public function ClassA()
{
initialize();
}
private function initialize():void
{
dispatcher = new EventDispatcher(this);
}
public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void
{
dispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference);
}
public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void
{
dispatcher.removeEventListener(type, listener, useCapture);
}
public function dispatchEvent(event:Event):Boolean
{
return dispatcher.dispatchEvent(event);
}
public function hasEventListener(type:String):Boolean
{
return dispatcher.hasEventListener(type);
}
public function willTrigger(type:String):Boolean
{
return dispatcher.willTrigger(type);
}
}
}
Note: we pass a reference to aggregating class to dispatcher constructor.
This is done to make event.target reference your class instance and not the dispatcher instance itself.
Pros: you are free to extend whatever you like. You may do some tricks with dispatcher hooks like maintaining listeners list or something alike.
Cons: not as simple as the first approach.
2) How do I pass custom data with my events?
General options are: pass data in an event instance, or only use event.target reference in event handler to access some data from source.
If you choose to access all necessary data through event.target -- no additional work nedded, just cast this reference in event handler to appropriate class.
If you want to pass some data along with event, you subclass Event, and this class should be publicly visible to the code that handles events, as the answer above states. AS3 is all about strict and strong typing, so why would you resist that?
Overriding clone() method in an Event subclass is only necessary if you are going to redispatch handled events. The official docs say you must do that every time you create a custom event class, just to be safe.
don't forget to override clone. it's also a good idea to override toString for debugging.
here's an example of one of my custom events:
package com.mattie.events
{
//Imports
import flash.events.Event;
//Class
public class SearchFieldEvent extends Event
{
//Constants
public static const SEARCH_COMPLETE:String = "search complete";
//Variables
public var totalResults:uint;
public var duration:uint;
public var searchText:String;
//Constructor
public function SearchFieldEvent(type:String, totalResults:uint = 0, duration:uint = 0, searchText:String = "")
{
super(type);
this.totalResults = totalResults;
this.duration = duration;
this.searchText = searchText;
}
//Override clone
public override function clone():Event
{
return new SearchFieldEvent(type, totalResults, duration, searchText);
}
//Override toString
public override function toString():String
{
return formatToString("SearchFieldEvent", "type", "totalResults", "duration", "searchText");
}
}
}
Related
I have a class "Shop" through which I'm trying to dispatch an event. Previously, I've done with another class "Buyer" and it works but the same thing doesn't work for my new class.
Function of Shop Class that is dispatching event:
public function Select(event:MouseEvent):void
{
trace(ShopName);
dispatchEvent( new SelectEvent(SelectEvent.SHOPSELECT,null,this));
}
The code when I run it , displays the "Shop Name" efficiently through trace but it doesn't seem to dispatch the event or else, it is dispatched but not reccieved well.
Reccieving code for Shop class dispatched event:
addEventListener(SelectEvent.SHOPSELECT,shopselected,true);
Buyer Class' function where the same method worked:
public function Select(event:MouseEvent):void
{
trace(buyerCode);
if(y>=377|| enteredmall)
{
dispatchEvent( new SelectEvent(SelectEvent.BUYERSELECT,this,null));
}
}
Recceiving Code for the Buyer Class dispatched event:
addEventListener(SelectEvent.BUYERSELECT,showselectbuyer,true);
This works but Shop class' one doesn't.
The only difference between both the classes is that the CLICK is on different objects. The Buyer class's object when clicked dispatches the event but the Shop Class's movieclip when clicked dispatches the event.
I've learnt about bubbling of events and I think I'm doing it right in the code. I've read similar question that was asked here but I couldn't understand how to proceed with the solution as the answer was not clear to me.
How to make it work for the shop class as well?
Additional Information:
Select Event Class:
package
{
import flash.events.Event;
import flash.utils.getQualifiedClassName;
public class SelectEvent extends Event
{
public static const BUYERSELECT:String = "buyerselect";
public static const SHOPSELECT:String = "shopselect";
public var selectedbuyer:Buyer;
public var selectedshop:Shop;
public function SelectEvent(type:String, selectedbuyer:Buyer=null, selectedshop:Shop=null, bubbles:Boolean=false, cancelable:Boolean=false)
{
super(type,bubbles,cancelable);
if(selectedshop==null)
{
this.selectedbuyer= selectedbuyer;
}
else
{
this.selectedshop= selectedshop;
}
}
override public function clone():Event
{
return new SelectEvent (type, selectedbuyer, selectedshop ,bubbles, cancelable);
}
}
}
using robotlegs i always retrieve this error. I have not idea whats wrong.
TypeError: Error #1009: Cannot access a property or method of a null
object reference.
at org.robotlegs.mvcs::Actor/dispatch()[/Users/shaun/Documents/
Development/Workspaces/GanymedeFB4/robotlegs-framework/src/org/
robotlegs/mvcs/Actor.as:57]
Model
package com.something.model {
// someimports
public class PhotoModel extends Actor {
public function uploadAndDetect() : void {
// something
dispatch(new DetectEvent(DetectEvent.DETECTED));
}
}
}
Event
package com.something.events {
// someimports
public class DetectEvent extends Event {
public static const DETECTED : String = "DETECTED";
public function DetectEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false){
super(type, bubbles, cancelable);
}
override public function clone():Event{
return new DetectEvent(type, bubbles, cancelable);
}
}
}
I can't read German well enough to be sure, but I'd suspect that the issue is that your Model isn't getting injected with the Event Bus (IEventDispatcher shared by just about everything Robotlegs needs to communicate with).
How are you instantiating this? Are you running this from a Unit test? If so, you need to set the eventDispatcher on your Model. If not, you need to use mapClass, mapSingleton, or mapSingletonOf to make sure your Model gets instantiated with the things it needs to work as an Actor.
When and where are creating the Model? I bet that your are not injecting its dependencies.
The model should be created in a Command and the use:
var model: PhotoModel = new PhotoModel();
injector.injectInto(model);
And then the eventDispatcher (the only dependency of Actor) should be injected.
is it true that keyboard events can not be accessed outside of the stage on non display objects?
example:
package
{
//Imports
import flash.events.EventDispatcher;
import flash.events.KeyboardEvent;
//Class
public class TestClass extends EventDispatcher
{
//Constructor
public function TestClass()
{
init();
}
//Initialization
public function init():void
{
addEventListener(KeyboardEvent.KEY_UP, keyUpEventHandler);
}
//Key Up Event Handler
private function keyUpEventHandler(evt:KeyboardEvent):void
{
trace("Test Class: " + evt.keyCode);
}
}
}
here i would like to initialize a new TestClass() then press a on the keyboard to receive the output Test Class: a.
To my knowledge (and according to the livedocs example) you need to add a KeyboardEvent listener to a displayObject. I've done this in abstract and static classes by passing a reference to the stage (or any displayObject) to your class's init method or constructor.
So, for example, in your document class, you could do:
var testClass:TestClass = new TestClass();
testClass.init(stage);
and in TestClass.as do:
public function init(stageReference:DisplayObject):void
{
stageReference.addEventListener(KeyboardEvent.KEY_UP, keyUpEventHandler);
}
While I agree it's a little wonky, I don't think there's a way to do it without using a DisplayObject.
I've been looking into custom event (listeners) for quite some time, but never succeeded in making one. There are so many different methods, extending the Event class, but also Extending the EventDispatcher class, very confusing!
I want to settle with this once and for all and learn the appropriate technique.
package{
import flash.events.Event;
public class CustomEvent extends Event{
public static const TEST:String = 'test'; //what exac is the purpose of the value in the string?
public var data:Object;
public function CustomEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, data:Object = null):void
{
this.data = data;
super();
}
}
}
As far as I know a custom class where you set the requirements for the event to be dispatched has to be made:
package
{
import flash.display.MovieClip;
public class TestClass extends MovieClip
{
public function TestClass():void {
if (ConditionForHoldToComplete == true) {
dispatchEvent(new Event(CustomEvent.TEST));
}
}
}
}
I'm not sure if this is correct, but it should be something along the lines of this.
Now What I want is something like a mouseevent, which can be applied to a target and does not require a specific class.
It would have to work something like this:
package com.op_pad._events{
import flash.events.MouseEvent;
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.events.EventDispatcher;
import flash.events.Event;
public class HoldEvent extends Event
{
public static const HOLD_COMPLETE:String = "hold completed";
var timer:Timer;
public function SpriteEvent(type:String, bubbles:Boolean=true, cancelable:Boolean=false)
{
super( type, bubbles, cancelable );
timer = new Timer(1000, 1);
//somehow find the target where is event is placed upon -> target.addEventlistener
target.addEventListener(MouseEvent.MOUSE_DOWN, startTimer);
target.addEventListener(MouseEvent.MOUSE_UP, stopTimer);
}
public override function clone():Event
{
return new SpriteEvent(type, bubbles, cancelable);
}
public override function toString():String
{
return formatToString("MovieEvent", "type", "bubbles", "cancelable", "eventPhase");
}
//////////////////////////////////
///// c o n d i t i o n s /////
//////////////////////////////////
private function startTimer(e:MouseEvent):void
{
timer.start();
timer.addEventListener(TimerEvent.TIMER_COMPLETE, complete);
}
private function stopTimer(e:MouseEvent):void
{
timer.stop()
}
public function complete(e:TimerEvent):void {
dispatchEvent(new HoldEvent(HoldEvent.HOLD_COMPLETE));
}
}
}
This obviously won't work, but should give you an idea of what I want to achieve. This should be possible because mouseevent can be applied to about everything.The main problem is that I don't know where I should set the requirements for the event to be executed to be able to apply it to movieclips and sprites.
You are almost there actually, just for the last part, wouldn't this be more of an OOP related issue than stricly a confusion about the way of using custom events ?
Usually, Events in AS3 are value objects whose sole responsibility is to transport information from the event dispatcher to the listener(s). The dispatcher dispatches the event each time a defined momentum is reached, and the listener(s) may or may not react when this happens.
In the example above, I guess it is up to the listener to start a timer and so on when a mouse-down has been detected. In a more sophisticated context the Event could independently trigger more than one listeners actioning separate tasks which neither the Dispatcher nor the Event itself should have to bother about, that is probably why it's worth avoiding amending the dispatcher or the event itself with any soever logic.
For your very example, you could maybe create a handler checking if the mouse has been held down?
The following is just pseudocode, and there are obviously tons of other ways to get to the same result:
public class MouseDownHandler
{
// ...
public function( target:Sprite ) {
this.target = target;
start();
}
public function start():void{
// Listen for the target's mouseUp event
}
public function dispose():void{
// Stop listeners and eventually the timer
}
private function onMouseDown(e:MouseEvent):void{
// Start timer + listening for the stage's mouse up event (target.stage)
}
private function onMouseUp(e:Event):void{
// Cancel timer
}
private function onTimerComplete(e:TimerEvent):void {
dispatchEvent(new HoldEvent(HoldEvent.HOLD_COMPLETE));
}
}
Which could be reused for example this way:
var mc:MovieClip = new MovieClip(); ...
var mouseHandler:MouseDownHandler = new MouseDownHandler(mc);
mouseHandler.addEventListener(HoldEvent.HOLD_COMPLETE, onMcHoldComplete);
... or this way :
public class TestMovieClip extends MovieClip
{
private var mouseHandler:MouseDownHandler;
public function TestMovieClip() {
mouseHandler = new MouseDownHandler(this);
mouseHandler.addEventListener(HoldEvent.HOLD_COMPLETE, onMouseHoldComplete);
}
private function onMouseHoldComplete(e:HoldEvent):void {
// Do something
}
}
I just use robber penners signals. Very easy to use.
http://github.com/robertpenner/as3-signals
This is a question specifically about creating a custom Loader class as a followup to How do I modify existing AS3 events so that I can pass data?. If this wasn't the right way to go about this, let me know.
I have a custom Loader class and a custom LoaderInfo class. What I can't figure out is how to have the Loader class use the custom LoaderInfo class as it's contentLoaderInfo property.
package com.display {
import flash.display.Loader;
import com.display.CustomLoaderInfo;
public class CustomLoader extends Loader {
public var customData:*;
public function CustomLoader(passedData: *= null) {
customData = passedData;
}
}
}
^ Have to do something in there to make it have the new CustomLoaderInfo
package com.display {
import flash.display.LoaderInfo;
import flash.events.Event;
import com.events.CustomEvent;
public class CustomLoaderInfo extends LoaderInfo {
public var customData:*;
public function CustomLoaderInfo(passedData: *= null) {
customData = passedData;
}
override public function dispatchEvent(event:Event):Boolean {
var customEvent:CustomEvent = new CustomEvent(event.type, customData, event.bubbles, event.cancelable);
return super.dispatchEvent(customEvent);
}
}
}
^ That might work, but since I can't get CustomLoader to use it, I don't know yet.
I don't know how it fits into what you're doing, but you could not bother making an extended LoaderInfo at all. Loader.contentLoaderInfo has a property called loader that will return the Loader (or in this case CustomLoader) that was used. In theory you can just do this with the CustomLoader class that you have:
var loader: CustomLoader = new CustomLoader("Extra Data");
loader.load(new URLRequest("file.swf"));
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loaderComplete);
function loaderComplete(event: Event) : void
{
var customData:* = (event.target.loader as CustomLoader).customData;
trace(customData); // Extra Data
}
You would have to override the function where the loaderinfo instance is created, and that might be in a private function. In the new function you could then instanciate your custom loaderInfo class instead, but you would also have to do anything else that the overwritten method does. You might run into some security sandbox problems with loadinfo as well. Overwriting a function like this is usually only posible when the function written with this in mind.
Alternatively you could use the prototype chain (read here) to change the loaderInfo class at runtime. But I wouldn't recommend it. It's not good practice in my opinion.
If you get this to work you still have the problem that your custom event will not work with any event that has properties not inheritet from the Event class (like the ProgressEvent that has progress related properties).
I would say you properly should look for another solution.
In the question you link to you talk about sending movieclip with your event. Have you thought about reaching the other way? When you recieve the event, you get a reference to the object that dispatched it (Event.target). Can you not use that reference to get the movieclip?
Perhaps you could explain your problem in more detail (maybe in a new question so you still keep this open), I am sure there is a better / easier way to solve it.
I needed something like this because I wanted to carry an index with the info ... ie - have something like event.target.index available after an event is triggered .. anyway i designed a class that contained the loader and the loader info... heres the class
public class LoaderBox extends Sprite {
public static var PROGRESS:String = "progress"
public static var COMPLETE:String = "complete"
public var id:int
public var index:int
public var loader:Loader
public var info:LoaderInfo
public var isOpen:Boolean
//
public var percent:Number = -1
public function load(path:String,idx:int=-1,nm:String=null){
loader = new Loader()
if (nm != null){
name = nm
}
index = idx
var req:URLRequest = new URLRequest(path)
info = loader.contentLoaderInfo
info.addEventListener(Event.COMPLETE,complete)
info.addEventListener(ProgressEvent.PROGRESS,progress)
isOpen = true
loader.load(req)
}
private function complete(e:Event):void{
isOpen = false
dispatchEvent(new Event(LoaderBox.COMPLETE))
}
private function progress(e:ProgressEvent):void{
if (e.target.bytesTotal>0){
percent = Math.floor(e.target.bytesLoaded/e.target.bytesTotal * 100)
}
dispatchEvent(new Event(LoaderBox.PROGRESS))
}
}
A potential issue with the flagged answer: if contentLoaderInfo spits out a IOError, you can't access the .loader property to access your custom loader class.
What I did is the following:
* in my custom loader class, create the following method:
public function requestError(event:Event):void {
dispatchEvent(event);
}
when adding a listener to contentLoaderInfo for the IOerror, point to the custom loader class's method:
_loaderCls.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, _loaderCls.requestError);
Then, add the same listener to your loader class and make it call any method you need. At that point, the event.target would be that of your custom loader class and you can add any extra info you need to that class:
_loaderCls.addEventListener(IOErrorEvent.IO_ERROR, requestError);
or you can do a better thing to pass data into the Loader class:
package com.display
{
import flash.display.Loader;
public class Loader extends flash.display.Loader
{
private var _obj:Object;
public function Loader():void
{
super();
}
public function get obj():Object
{
return _obj;
}
public function set obj(o:Object):void
{
_obj = o;
}
}
}