I have this custom event handler that shows a popup and accepts input from the user:
private var mySkinnablePopupContainer:MySkinnablePopupContainer;
private function handleShowGridPopupEvent(event:ShowGridPopupEvent):void {
var mouseDownOutSideHandler:Function = function(mdEvent:FlexMouseEvent):void {
// At this point, event.targetControl contains the wrong object (usually the previous targetControl)
if (mdEvent.relatedObject != event.targetControl) {
mySkinnablePopupContainer.close();
}
}
var gridPopupSelectionHandler:Function = function(popEvent:PopUpEvent):void {
if (!popEvent.commit) return;
// At this point, event.targetData contains the wrong object (usually the previous targetData)
myModel.doSomethingWithData(popEvent.data.selectedItem, event.targetData);
}
if (!mySkinnablePopupContainer) {
mySkinnablePopupContainer = new MySkinnablePopupContainer();
mySkinnablePopupContainer.addEventListener(PopUpEvent.CLOSE, gridPopupSelectionHandler);
mySkinnablePopupContainer.addEventListener(FlexMouseEvent.MOUSE_DOWN_OUTSIDE, mouseDownOutSideHandler);
}
// At this point, event.targetData contains the correct object
mySkinnablePopupContainer.dataProvider = getMyDPArrayCollection(event.targetData);
mySkinnablePopupContainer.open(this);
var point:Point = event.targetControl.localToGlobal(new Point());
mySkinnablePopupContainer.x = point.x + event.targetControl.width - mySkinnablePopupContainer.width;
mySkinnablePopupContainer.y = point.y + event.targetControl.height;
}
Every time the function handler gets called, it will have the correct ShowGridPopupEvent object but by the time it calls the
gridPopupSelectionHandler, it will contain the old object from a previous call. It works the first time, subsequent calls fails.
Somehow the reference to the event object changed somewhere in between before opening the popup and after.
Any idea what am I doing wrong here? Is this a bug with flex?
found the prob. since im attaching listener only once, it will reference the old listener, with the reference to the old data. i guess i was expecting its reference to be updated whenever i create the closure. not in this case. possible fix is to remove the listener and re-add it again but I abandoned the idea of using closures, and aside from what RIAStar mentioned, it is also impractical as it only gives more overhead by creating a new function for every invocation of the handler.
Related
Basically I want to check if a user exists in a database using AMF (and that works great!). But then I want to return the boolean value to another function (in another class) that originally called the "checkUserExistance" function. But, since the database connection isn't immidiate, this function will always return a false value (even if "result" is true). So I would like to have the return-line inside the "onUserChecked"-function but that of course gives me an error. I thought I could create an eventListener, but then, the "return userExists"-line would also have to be inside another function, which doesnät work(?)... What can I do?
public function checkUserExistance(username:String) {
var responderBOOLEAN:Responder = new Responder(onUserChecked, onFault)
var userExists:Boolean = false;
connection.connect(gateway);
connection.call("User.checkUser", responderBOOLEAN, username);
connection.close();
function onUserChecked(result:Boolean):void {
userExists = result;
}
return userExists;
}
I'm sorry but you are trying to force an Asynchronous call to a Synchronous one and this is WRONG.
See here
You should learn how to handle events in the correct way.
What can i suggest you that helped me a lot is this
The only true answer here is to save userExists as a member variable, and dispatch event when the server returns you a response. The client side of the things should be similar to:
// add listener, ServerEvent is a custom event (see below)
server.addEventListener(ServerEvent.CHECK_RESPONSE, onCheckResponse);
server.checkUserExistance('username'); // start the query
function onCheckResponse(e:ServerEvent):void {
if (e.userExists) {
}
}
// inside server class
function onUserChecked(result:Boolean):void {
userExists = true;
dispatchEvent(new ServerEvent(ServerEvent.CHECK_RESPONSE, userExists));
}
/* ServerEvent is a custom class that extens Event
Such classes are used so you can pass special properties in them
via constructor (pass data, store it into member variable)
and through getter for that variable.
If you don't like it, simply add/dispatch Event.COMPLETE
and use public property to get userExists from server
*/
I had a strange bug in my program which I fortunately found quite quickly but am still puzzled why it was happening. Essentially it was to do with the order of commands in the source code and event listeners, here is the example:
function detectFaces(loader:ImageLoader)
{
var detector:FaceDetector=new FaceDetector();
detector.addEventListener(FaceDetectorEvent.FACE_CROPPED,facesDetected);
detector.loadFaceImageFromBitmap(loader.bitmap);
var something:Number = stage.width;
function facesDetected(e:FaceDetectorEvent):void{
trace(something);
}
}
Operation that raise the event here is not important, only thing to note about it would be it takes around 100ms. What I get as trace output is NaN and I don't know why that is since line declaring the variable something will definitely be called before callback of facesDetected and it is in scope of the handler function declared under it. This problem was easy to solve by just moving var something:Number = stage.width; before loadFaceImageFromBitmap(..) method, but I would really like to know why this is happening?
I am suspecting this is not due to order of execution but has something to do with passingByValue and passingByRefrence deferences but don't know how would these cause an error like this.
EDIT: Now I am even more puzzled... This code works in any order of declaration:
timers();
function timers()
{
var timerTest:Timer = new Timer(100,1);
timerTest.addEventListener(TimerEvent.TIMER,onTime);
//BEFORE DECLARATION
timerTest.start();
var something:Number = stage.width;
function onTime(e:Event)
{
trace("SOMETHING :"+something);
}
}
timers();
function timers()
{
var timerTest:Timer = new Timer(100,1);
timerTest.addEventListener(TimerEvent.TIMER,onTime);
var something:Number = stage.width;
//AFTER DECLARATION
timerTest.start();
function onTime(e:Event)
{
trace("SOMETHING :"+something);
}
}
With regard to your initial question, actionscript will complete the execution of a block of code before it continues to execute subsequent lines. If there was nothing asynchronous happening in your loadFaceImageFromBitmap method (ie, if your weren't using a Loader or some other object that had to wait for an event to fire) then, however long the code takes to execute, the FACE_CROPPED event will still fire before 'something' is set to a value.
As for the other problem, it looks to me like the answer is simply that you're using a TimerEvent - Actionscript will acknowledge that it shouldn't wait for the event to fire before continuing to execute code; It will, therefore, declare 'something' before the 100 miliseconds passes. So, in this case, because you're using an event, the code WILL continue 'reading' and executing the lines following the event listener.
The code of the function loadFaceImageFromBitmap run on a sync way. The FaceDetectorEvent.FACE_CROPPED event listener is invoked inside of that function, it is not a callback declared to run after some response is returned for ie(http request).
In the case of the Timer it works as expected, because event listener is not invoked right at the start moment, it waits for X time.
QUESTION ONE:
In the following example, ive added a listener to the foundMic. My question is if i re-run the foobar.initMic(); to reinitialize the microphone will i end up with a dead event listener floating in memory, and would it be picked up by garbage collection? Does calling the .getMicrophone() just reattach the same resource to foundMic or does it destroy the old foundMic and create a new instance of foundMic in memory.
As in if its the exact same foundMic then adding the event listener will silently fail and not create a second listener. But if foundMic becomes a new instance then it would be creating a new listener. Then back to the question about garbage collection, would it be considered unreferenced for clean up?
public class foobar {
public static var foundMic:Microphone = null;
public static function initMic():void {
foundMic = Microphone.getMicrophone();
foundMic.codec = SoundCodec.SPEEX;
foundMic.setSilenceLevel(0, 5000);
foundMic.gain = 50;
foundMic.setUseEchoSuppression(true);
foundMic.soundTransform.volume = 1;
foundMic.addEventListener(StatusEvent.STATUS, onMicStatusEvent);
return;
}
public static function onMicStatusEvent(event:StatusEvent):void {
if (foundMic && !foundMic.muted) someButton.enabled = true;
else someButton.enabled = false;
return;
}
} // END CLASS
QUESTION TWO:
Based on question one, if i added a remove listener just before adding the listener, does that actually remove the listener on the second time ran which was created when the method was ran the first time? Or is that foundMic already a new instance from the .getMicrophone() so nothing is removed because the first listener is already floating in memory associated with the previous instance of foundMic?
public static function initMic():void {
foundMic = Microphone.getMicrophone();
.
.
.
foundMic.removeEventListener(StatusEvent.STATUS, onMicStatusEvent);
foundMic.addEventListener(StatusEvent.STATUS, onMicStatusEvent);
return;
}
foundMic will just be assigned a new value which is a reference to to the Microphone - nothing should be garbage collected.
A case where the garbage collector should kick in is something like this:
var someVar:Sprite;
for(var i:int = 0; i < 5; i++)
{
someVar = new Sprite();
}
In this case you've created 5 new Sprite objects but ended up with only one assigned to the someVar variable - the other 4 should get garbage collected at some point because they are not referenced by any variable.
As far as question two, the code you have should give you an error the first time you call initMic() since at that point there is no onMicStatusEvent listener yet. Before trying to remove the event listener first test to see if it exists:
if(foundMic.hasEventListener(StatusEvent.STATUS)
{
foundMic.removeEventListener(StatusEvent.STATUS, onMicStatusEvent);
}
foundMic.addEventListener(StatusEvent.STATUS, onMicStatusEvent);
This code will always remove the event listener first if it exist and then subsequently add a new event listener. However, in this case I don't see any reason for this, as addEventListener by itself should work just fine - multiple calls are all on the same object, so no matter how many times you call initMic() you should only have one event listener running - there's no need to manually remove the listener and then recreate.
Here is what happens with event listeners. someObject.addEventListener('someEvent', someFunction) hands someObject a reference to someFunction. Because of the way scope works in ActionScript, the function will have access to everything in the scope of the code that created it. So until you call removeEventListener, the instance where the function exists will be held in memory as long as someObject is in memory.
However, in your case, there is no instance, because by using static functions your listeners will exist from the time your Class FooBar is first referenced until your swf is taken out of the computer's memory.
There is no net effect of removing the listener prior to adding it, because it is exactly the same function being handed to exactly the same object, which isn't going to call your function twice.
For more on how event listeners work in detail, check out this blog post.
Hello i have a function as following:
private function seatClickHandler(e:MouseEvent):void{
var check:Check = new Check();
if(e.target.contains(check)){
e.target.removeChild(seat);
}else{
e.target.addChild(check);
}
}
basicly i want to check if e.target contains a child called check. If it does i want e.target to remove the child, else i want to add the child. But the method i tried doesnt seem to work although i think this is the way to go. Any suggestions?
When you declare your Check object, Actionscript creates a reference code for that specific object.
So the first time your code is run, your Check object could be given a reference of #c0ecc29. Your if statement checks to see if #c0ecc29 is a child component of target. It won't be, so the Check object with reference #c0ecc29 is added to target.
The second time the clickHandler is called, a new instance of the Check object is created which will have a new reference id. Your target has the original Check object with the #c0ecc29 reference so it won't get removed.
The correct way to get this working depends on what target is (DataGrid, Group, etc.).
EDIT:
Based on your comments, I would try something like this. It checks to see if the Check object is a child of target and adds it if needed. Then when the Check object is clicked, it will toggle its visibility.
public var check:Check = new Check();
private function seatClickHandler(e:MouseEvent):void
{
if(!e.target.contains(check))
{
check.addEventListener(MouseEvent.CLICK, check_handleClick);
e.target.addChild(check);
}
}
protected function check_handleClick(event:MouseEvent):void
{
check.visible = !check.visible;
}
If you need to actually remove the Check object from target instead of just changing its visibility, you could try this:
public var check:Check = new Check();
private function seatClickHandler(e:MouseEvent):void
{
if(!e.target.contains(check))
{
e.target.addChild(check);
}
else
{
e.target.removeChild(check);
}
}
If the child is named 'check' then you should be able to use getChildByName(). See flash.display.DisplayObject.name
If you happen to have the child in memory, you can use getChildIndex()
check is a new object in the scope of that function, so it will not be a child of the event target.
What you want to do is declare check as a global variable (And also cast target as DisplayObjectContainer).
e.g.
private function seatClickHandler(e:MouseEvent):void{
if((e.target as DisplayObjectContainer).contains(check)){
(e.target as DisplayObjectContainer).removeChild(seat);
}else{
(e.target as DisplayObjectContainer).addChild(check);
}
}
However I'm not sure if this is exactly what you want to do (There can only be one check). A better approach would be to have a function (maybe toggleCheck) on the target, and have that display object responsible for rendering the check (And removing it)
This worked perfectly fine for me in my situation:
if(possibleChild.parent == holder)
holder.removeChild(possibleChild)
It may or may not be exactly what you're looking for.
I have a strange issue! I am trying to remove an event listener on a FileReference object by calling a function, but it seems not to be removed, and I do not understand why.
Here is the code:
private function clearFileUploadListeners(file:FileReference, index:String):void {
var dispatchEvent:Function = function(event:Event):void {
dispatch(event.type, event, index);
};
file.removeEventListener(Event.COMPLETE, dispatchEvent);
var bool:Boolean = file.hasEventListener(Event.COMPLETE);
if (bool)
trace("ERROR");
}
When I run this code, the trace actually happens. I don't understand why this boolean returns true, when I just tried to remove the eventListener just above! I guess I am probably doing something really stupid because it seems like a strange error.
I hope someone can please help me on this issue.
EDIT:
I believe it has to do with the fact that the dispatchEvent function is defined inside another function when I add the listener:
private function upload(file:FileReference, index:String):void {
var dispatchEvent:Function = function(event:Event):void {
dispatch(event.type, event, index);
};
file.addEventListener(Event.COMPLETE, dispatchEvent);
}
The problem is that I need to access this "index" variable from the listener, and I can't set it as a global variable as each file has it's own index and it's a burden if I have to extend each event class to keep track of the index (Event, ProgressEvent, ..). I hope someone can please help me on this.
EDIT2:
I actually found a temporary solution, I am not sure if it is the best! I put my removeListener method actually inside the upload method, but made it a variable. As AS3 allows dynamic object, I attached this method to one of my object, and so I just call the reference to the method when necessary. The event is actually removed. Is this a good solution please?
Thank you very much,
Rudy
You're right, it has to do with the fact that you're defining a function inside another function, then using it to handle events.
Each time the function upload is called, it creates a new closure, and assigns a reference to it to the dispatchEvent variable, which is then passed to the addEventListener class. So each time upload is called, it is using a new, different closure in the call to addEventListener. Similarly, in the clearFileUploadListeners function, a new closure is being created on each call (which happens to have the same code each time, but isn't the same function object). The call to removeEventListener does nothing if the given callback has not been added as an event listener for the given event, which is the case here.
To solve your problem, you need to store a reference to the closure that you pass to the addEventListener function. This way, you can get a reference to the same closure that was added when you need to remove it later in clearFileUploadListeners.
You can try something along the lines of the following code (untested):
import flash.utils.Dictionary;
var callbackRegistry:* = new Dictionary();
private function upload(file:FileReference, index:String):void {
var dispatchEvent:Function = generateFileUploadCompleteCallback();
callbackRegistry[file] = dispatchEvent;
file.addEventListener(Event.COMPLETE, dispatchEvent);
}
private function clearFileUploadListeners(file:FileReference, index:String):void {
var dispatchEvent:Function = callbackRegistry[file];
callbackRegistry[file] = null;
file.removeEventListener(Event.COMPLETE, dispatchEvent);
var bool:Boolean = file.hasEventListener(Event.COMPLETE);
if (bool)
trace("ERROR");
else
trace("YAY, ALL OK!");
}
private function generateFileUploadCompleteCallback(index:String):Function {
return function(event:Event):void {
dispatch(event.type, event, index);
};
}
Two other things to note on this subject.
If you must utilize a native Event directly then you should pretty much always make sure and use these last three optional params :
myObject.addEventListener( Event.COMPLETE, myFunction, false, 0, true );
Check Grant Skinner's post on the subject here :
http://gskinner.com/blog/archives/2006/07/as3_weakly_refe.html
And the very best practice of all is to ALWAYS (seriously always) use Robert Penner's Signals (instead of custom events) and his NativeSignals (to wrap needed native Flash events).
Five times faster than Flash's native events.
Always safe with weak references.
Any number of typed payload(s) in each Signal.
Get the SWC here :
https://github.com/robertpenner/as3-signals
Signals were designed to solve the very problem you are having.
Imagine instead of creating an array and managing that to remove all listeners if you could just call :
signalBtnClicked.removeAll();
or
signalBtnClicked.addOnce( function( e : MouseEvent ) : void { /* do stuff */ } );
Knowing that the closure you just created will immediately be dereferenced once it is called and happily go night night when the GC makes its rounds.