Can anyone provide an example of how to write a callback instead of using an event to communicate between two classes (objects) in Actionscript 3.0?
Just pass a function to another one as parameter to make your callback :
class A {
function A(){
}
// function to be called when work is finished
private function workDone():void {
//...
}
public function foo():void {
var b:B=new B();
b.doWork(workDone); // pass the callback to the work function
//can also be an anonymous function, etc..
b.doWork(
function():void{
//....
}
);
}
}
class B {
function B(){
}
public function doWork(callback:Function):void{
// do my work
callback(); // call the callback function when necessary
}
}
What do you mean? A callback is a function that is called in response to an event - in AS parlance, it's an event listener. If you just want classes to communicate, have one of them call a method on the other.
Related
While experimenting with a basic coding stuff, I wondered, if a value returned by a method called by a delegate, could be used or captured. In other words, where will the return value will go ?
For example :
class Main extends Sprite
{
public var mc:MyMc;
function Main()
{
mc.addEventListener( "myClick" , myClick);
}
function myClick(e:Event):String //returning a string
{
return "What happens to this return value ???"
}
}
class MyMc extends MovieClip
{
function MyMc()
{
addEventListener( MouseEvent.CLICK , onClick);
}
function onClick(e:Event):String //returning a string
{
dispatchEvent(new Event("myClick"));
}
}
As I know it's not possible to do, but, there are at least some ways to implement the logic similar to what you've told about.
For example, you may call a method of a dispatcher, from a listener method:
class Main extends Sprite
{
public var mc:MyMc;
function Main()
{
mc.addEventListener("myClick" , myClick);
}
function myClick(e:Event):void
{
mc.specialMethod("some string");
}
}
class MyMc extends MovieClip
{
function MyMc()
{
addEventListener(MouseEvent.CLICK , onClick);
}
function onClick(e:Event):void
{
dispatchEvent(new Event("myClick"));
}
public function specialMethod(param:String):void
{
// Do something to the param
}
}
Also, you may think about dispatching an event from the Main class, and listen to it in the MyMc class, or pass a callback, which returns a string, from Main to the MyMc.
It's according to you and your needs to return something from the listener function because normally it must return nothing :
... This function must accept an Event object as its only parameter and must return nothing, ...
but you can of course get the returned value(s), take a look on this little example :
var light_on:Boolean = false;
btn_light_on.addEventListener(MouseEvent.CLICK, btn_on_onPress);
btn_light_off.addEventListener(MouseEvent.CLICK, btn_off_onPress);
function btn_on_onPress(e:MouseEvent): Boolean {
light_on = true;
if(e.target === btn_light_off){
light_on = false;
}
return light_on;
}
function btn_off_onPress(e:MouseEvent): void {
trace('The light is', btn_on_onPress(e) ? 'on' : 'off');
}
Hope that can help.
Is it possible to pass a var at the end of an addEventListener?
/// clickType declared elsewhere in code.
checkBoxFast.addEventListener(clickType, goFast("yes"));
function goFast(evt:Event=null,myVar:String)
{
trace(myVar);
}
I guess if you want to parametrize your event handing I would suggest passing variables to the Event.
-Create a custom event:
public class MyEvent extends Event {
public var myVar:String;
public function MyEventHistoryEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false) {
super(type, bubbles, cancelable);
}
}
-Dispatch this event from the event dispatcher with the required variable:
var event:MyEvent = new MyEvent("eventType");
event.myVar = "yes";
dispatchEvent(event);
-Add an event handler:
checkBoxFast.addEventListener("eventType", eventHandler);
protected function eventHandler(event:MyEvent):void {
trace(event.myVar);
}
Another solution would be to use an anonymous function like so:
checkBoxFast.addEventListener(clickType, function(e:Event):void{goFast("yes")});
function goFast(myVar:String)
{
trace(myVar);
}
Creating custom event is best way I guess. But I was using sometimes different aproach. I dont know if it is good practice but it works in some cases.
public function test() {
var myVar : String = "some value";
addEventListener(MouseEvent.CLICK, onClick);
function(e:Event){
trace(myVar);
}
}
Here's a pretty clean way:
checkBoxFast.addEventListener(clickType, goFast("yes"));
function goFast(myVar:String) {return function(e:Event) {
trace(myVar);
}}
BUT beware anonymous functions, they won't let you end the listener in the same place it was made! If you keep repeating it like that many times in your application, it may get slow and freeze.
Actually, I really recommend you to do it like this:
var functionGoFast:Function = goFast("yes");
checkBoxFast.addEventListener(clickType, functionGoFast);
function goFast(myVar:String):Function {
return function(evt:Event = null):void {
trace(myVar);
}
}
//checkBoxFast.removeEventListener(clickType, functionGoFast);
See this answer for more examples and explanations on your case.
Object A calls method M of object B, passing it two callbacks for two cases: cbYes and cbNo.
B, in turn, performs a web service async call, creating Object C (api) instance with the only callback: method N of B. This callback will decide which of the two callbacks to call.
I store cbYes and cbNo functions as B's private vars of type Function.
How can I call either callback? They're not children of B, so syntax B[cbYes](); is not the way. Unreal code example:
class A {
public function Smth() {
var instB:B = new B( cbYes, cbNo);
}
public function cbYes( e:Event) { doSomething(); }
public function cbNo( e:Event) { doSomething(); }
}
class B {
private var _cb1:Function;
private var _cb2:Function;
public function B( cb1, cb2) {
_cb1 = cb1; _cb2 = cb2;
var worker:C = new C();
C.apiMethod123( cbAfterCall);
}
public function cbAfterCall( Result:*) {
if( Result = 1) {
// here I need to call callback from _cb1
} else {
// here I need to call callback from _cb2
}
}
}
class C {
private var _Callback:Function;
public function C() { }
public function apiMethod123( cb:Function) {
this._Callback = cb;
// create a URLLoader or a Loader and do a web service call
}
public function urlCallback( e:Event) {
// parse response
this._Callback();
}
}
Ok, while I was putting together this sample code, I realised I already solved this with the api caller worker! :-) Got to have more sleep.
AfterQuestion: does this architectural approach seems really wrong? Please advice a better one, or a pattern that suits the system where concurrent asynchronous API calls are used.
If you store cbYes and cbNo as member variables inside B. You can call them like normal functions:
public function foo(cbYes:Function,cbNo:Function):void
{
this.cbYes = cbYes;
this.cbNo = cbNo;
}
public function bar():void
{
cbYes();
}
Instead of using callbacks like this you can use the event system to achieve the same result. You can create custom events and dispatch the yes/no event.
Since when using sql lite if you try and do a function at the same moment it throws an error, im just trying to make a function that will check if its executing, and if it is try again in 10 milliseconds, this exact function works fine if i dont have to pass any arguments to the function but im confused how I can pass the vars back into the function it'll be executing.
I want to do:
timer.addEventListener(TimerEvent.TIMER, saveChat(username, chatBoxText));
But it will only allow me to do:
timer.addEventListener(TimerEvent.TIMER, saveChat);
It gives me this compile error:
1067: Implicit coercion of a value of
type void to an unrelated type
Function
How can I get this to pass this limitation?
Here's what I've got:
public function saveChat(username:String, chatBoxText:String, e:TimerEvent=null):void
{
var timer:Timer = new Timer(10, 1);
timer.addEventListener(TimerEvent.TIMER, saveChat);
if(!saveChatSql.executing)
{
saveChatSql.text = "UPDATE active_chats SET convo = '"+chatBoxText+"' WHERE username = '"+username+"';";
saveChatSql.execute();
}
else timer.start();
}
A function called by a listener can only have one argument, which is the event triggering it.
listener:Function — The listener function that processes the event.
This function must accept an Event
object as its only parameter and must
return nothing, as this example
shows:
function(evt:Event):void
Source
You can get around this by having the function called by the event call another function with the required arguments:
timer.addEventListener(TimerEvent.TIMER, _saveChat);
function _saveChat(e:TimerEvent):void
{
saveChat(arg, arg, arg);
}
function saveChat(arg1:type, arg2:type, arg3:type):void
{
// Your logic.
}
Another thing you can do create a custom event class that extends flash.events.Event and create properties that you need within.
package
{
import flash.events.Event;
public class CustomEvent extends Event
{
// Your custom event 'types'.
public static const SAVE_CHAT:String = "saveChat";
// Your custom properties.
public var username:String;
public var chatBoxText:String;
// Constructor.
public function CustomEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false):void
{
super(type, bubbles, cancelable);
}
}
}
Then you can dispatch this with properties defined:
timer.addEventListener(TimerEvent.TIMER, _saveChat);
function _saveChat(e:TimerEvent):void
{
var evt:CustomEvent = new CustomEvent(CustomEvent.SAVE_CHAT);
evt.username = "Marty";
evt.chatBoxText = "Custom events are easy.";
dispatchEvent(evt);
}
And listen for it:
addEventListener(CustomEvent.SAVE_CHAT, saveChat);
function saveChat(e:CustomEvent):void
{
trace(e.username + ": " + e.chatBoxText);
// Output: Marty: Custom events are easy.
}
Actually, you can pass additional parameters to an event listener without creating a custom event by using Actionscript's dynamic function construction.
private function addArguments(method:Function, additionalArguments:Array):Function
{
return function(event:Event):void {method.apply(null, [event].concat(additionalArguments));}
}
When setting up the closeHandler for the Alert window we call the addArguments() method and pass in an array continaing all of the parameters we want to pass to the closeHandler. The addHandler() method will return the function we will call when the Alert window closes with the parameters included.
protected function btnOK_clickHandler(event:MouseEvent):void
{
Alert.show("Would you like to reverse the text you just entered?", "", Alert.YES | Alert.NO, null, addArguments(alertCloseHandler, [txtInput.text]), null, Alert.YES);
txtInput.text = "";
}
1067: Implicit coercion of a value of type void to an unrelated type Function
Pay due attention to the error you got: it says that a Function is a type and that addEventListener() wants it. Although your listener returns void, it is a Function! So, what about returning the listener?
timer.addEventListener(TimerEvent.TIMER, saveChat(username, chatBoxText));
function saveChat(username:String, chatBoxText:String):Function {
return function(e:TimerEvent):void {
// Do everything that uses "e", "chatBoxText" and "username" here!
};
}
Simple like this. It works for any kind of event. And no closure issues.
Note on removeEventListener():
Don't try to do timer.removeEventListener(TimerEvent.TIMER, saveChat(username, chatBoxText)). It won't recognize your function because you'll be passing a new one on it every time you use saveChat(). Instead, just take its reference out into a variable and you're done:
var functionSaveChat:Function = saveChat(username, chatBoxText);
timer.addEventListener(TimerEvent.TIMER, functionSaveChat);
//trace(timer.hasEventListener(TimerEvent.TIMER));
timer.removeEventListener(TimerEvent.TIMER, functionSaveChat);
//trace(timer.hasEventListener(TimerEvent.TIMER));
You can try this:
var timerHandler:Function = function (event:TimerEvent):void
{
saveChat(username,chatBoxText,event);
}
timer.addEventListener(TimerEvent.TIMER, timerHandler);
Above calling saveChat(arg, arg, arg) it's not for me, i need to pass arguments that i dont have in this method but i have another solution
Im always using additional method passParameters to adding new arguments:
public function passParameters(method:Function,additionalArguments:Array):Function
{return function(event:Event):void{
method.apply(null, [event].concat(additionalArguments));}
}
explanation of this is here - its simple and always work
http://sinfinity.pl/blog/2012/03/28/adding-parameters-to-event-listener-in-flex-air-as3/
I've asked this same question with Python.
Now I like to know if this can be done in AS3.
If I have something like this:
package
{
public class SomeClass
{
private function A():void { C() }
private function B():void { C() }
private function C():void
{
// who is the caller, A or B ???
}
public function SomeClass()
{
A()
B()
}
}
}
Despite the design or other issues, this is only a question of an inquiring mind.
Note: I like to have an access to an instance of the caller function so I can call that caller function (if I want to)
Note 2 : This has to be done without changing function C() signature
"Unlike previous versions of ActionScript, ActionScript 3.0 has no arguments.caller property. To get a reference to the function that called the current function, you must pass a reference to that function as an argument."
From http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/arguments.html
That's the only way you can do that, otherwise you'll need to make a global variable to tell what function is calling C
Sure it can be done. You can do something like
private function C():void
{
var e:Error = new Error();
var stack:String = e.getStackTrace();
//analyze stack and find out which function called it.
}
this is ugly but it would work.