How to remove this unrelated block of code, that checks child's availability in the container - actionscript-3

This is my code on button click. It adds myView to the container. And takes care of fact that all previous instances get removed before adding again. I wanna know, if there is some better way of making this "check" ? I especially wanna have some way to remove the 1s 7 lines of this code ( within comments). It seems like an unrelated block of code in the normal code-flow.
function button_CLICK(e:Event)
{
///////////////////////////////////////////////////
if ( myView!= null)
{
if ( contains(myView))
{
removeChild(myView) ;
}
}
/////////////////////////////////////////////////////
myView = new myView("hello")
addChild(myView);
}

it would depend on what the object myView consists of. if, for example, it has its own event listeners you would want to use the condition check to clean up the object before disposing of it so there are no memory leaks.
function button_CLICK(e:Event):void
{
if (myView && contains(myView))
{
//clean up myView
removeChild(myView);
}
myView = new myView("hello");
addChild(myView);
}
however, if myView is a simple display object that doesn't contain any leakable references or event listeners than you could simply reassign the variable to overwrite the previous.
keep in mind that addChild() places the added display object on top above all others. but regardless if that's something you'd like to avoid or not, i believe it would be better to assign a default or empty view once instead of addChild(myView) each time the button is clicked.
function button_CLICK(e:Event):void
{
myView = new myView("hello");
}
i assume your code is for for demonstration because new myView("hello") is going to always be the same object? unless perhaps this object relies on dates or timers to make instances different from previous constructs.

The checking code you have could create a memory leak as you don't null the previous myView before creating a new instance. NB. It's good practice to begin the name of your classes with an uppercase letter.
Your code also seems to allow for the button_CLICK method to be called when myView has been added. Perhaps consider hiding the button when the view has been added.
You could just have one instance of the MyView Class instantiated. Then rather than having to null and create a new instance each time, just add the same one and add an init function that will accept the initialising parameters. e.g.
var myView = new MyView();
function button_CLICK(e:Event)
{
if ( contains(myView))
{
removeChild(myView) ;
}
}
myView.init("hello");
addChild(myView);
}

I don't think you have much of an option, based on the (IMO) flawed implementation of DisplayObjectContainer. If you're feeling feisty, you can "fix" removeChild to see if the child is there before calling the super method, rather than simply throwing an error, as Adobe thought was the appropriate solution.
Your code above will still run, but it will be in the removeChild override.

Your commented bloc of code is necessary only for the first call to button_CLICK (you sould rename this method : buttonClick).
After the firts call, this bloc of code is unnecessary.
In order to remove this code myView should be not null, and an instance of MyView should be added on the display list.
var myView : MyView;
function init(){
myView = new MyView("FAKE");
myView.visible = false;
addChild(myView);
addEventListener("click",click);
}
function click(e:MouseEvent):void{
removeChild(myView);
myView = new Child("REAL");
addChild(myView);
}

Try:
if(myView != null)
myView.parent && myView.parent.removeChild(myView);

Related

in AS3, removeEventListener(Event.ENTER_FRAME) is not working

I have been dealing with this problem for days already. I am at my wits' end!
I can't seem to find a definitive answer anywhere on any of the forums, documentation, etc.
Everything looks fine at first run, or when I load a next level for the user to play. But if the user hits the ESC key to load a different level, the ENTER FRAME listener does not get removed and it duplicates all the triggers in it, showing the player going really fast, and all funky, because it builds on top of the previously instantiated ENTER FRAME listener.
I don't know if I have a problem of an anonymous function, or an unknown instance being referenced in my removeEvent... command... Bottom line, I give up and I need this working HELP!!!
Here's the code:
function initPlay():void
{
//code here determining what display object to add to the list and assign it to the currentLevel variable (a movieclip)
if(userIsLoadingOtherLevel){
removeEnterFrameListener();
addChild(currentLevel);
}
if(userIsGointToNextLevel)
addChild(currentLevel);
currentLevel.addEventListener(Event.ENTER_FRAME, onEnterFrame);
function onEnterFrame(event:Event):void
{
//collision detection, parallax scrolling, etc, etc is done here.
if(allCoinsCollected)
loadNextLevel();
if(ESCKeyPressed)
ESCKeyPressHandler();
}
function loadNextLevel():void
{
removeChild(currentLevel);
newLevelToLoad++
removeEnterFrameListener();
initPlay();
}
function ESCKeyPressHandler():void
{
removeChild(currentLevel);
initPlay();
}
function removeEnterFrameListener();
{
currentLevel.removeEventListener(Event.ENTER_FRAME,onEnterFrame)
trace("currentLevel.hasEventListener(Event.ENTER_FRAME) = "+currentLevel.hasEventListener(Event.ENTER_FRAME)); //outputs TRUE if called from loadNextLevel but FALSE if called from initPlay() !!!
}
}
I also tried to add and remove the eventListener to stage, MovieClip(Root), or nothing at all and the result is always the same.
I know that there may be other ways to design such a process, but please note I am not really flexible at the moment on doing this because the project is very long (about 4000 lines of code) and removing the ENTER FRAME this way, crazy or not should still work!!
THANK YOU in advance for anyone willing to help.
The problem appears to be the nested functions inside the initPlay() method.
Each time you call initPlay() you are defining new functions. Some of these nested functions call initPlay() themselves.
Functions are objects (memory references). So each time you call initPlay() you are making new references to new functions. So when you try to remove an event listener, you're only able to remove one of these event handlers (the one in the current scope of execution).
I'm not sure if I'm explaining this clearly, perhaps this example will help. I'll use numbers to represent the references to each function, and a simple scenario that is similar to yours:
function example():void
{
addEventListener(MouseEvent.CLICK, mouseClickHandler);
function mouseClickHandler(event:Event):void
{
if (someCondition)
{
example();
}
else
{
removeEventListener(MouseEvent.CLICK, mouseClickHandler);
}
}
}
When we run this function the first time, a new function is defined within the scope of the example() function. Lets use the number 1 to represent the reference to this nested function. someCondition is true on the first time around, and so the example() function is called again.
On the second execution of the example() function, a new reference to the mouse event handler is created (#2). We also add the event listener again. At this point, there are two event handling functions in memory, and both will be executed when the event is dispatched.
Let's say that in the second invocation of example() that someCondition is false and now we want to remove the listener. When we call:
removeEventListener(MouseEvent.CLICK, mouseClickHandler);
It's referring to event handler #2. Event handler #1 still exists, and because it's hidden in the scope of the first invocation of example() it can't be removed here.
My simple example breaks down after this... but I hope it makes it clear why your event handlers shouldn't be nested inside a function. Admittedly, this is difficult to describe and even more so in a real world example like yours. But I'm pretty confident that this is the source of most, if not all, of the issues you describe.
Here's how I was able to get around this without changing the scope of the nested functions (although I agree that would be the preferred solution) by creating a boolean variable called "loadingNewGame" and changing it to true from outside the onEnterFrame (in fact, this assignment was done from initPlay() and then from onEnterframe I called removeEnterFrameListener() function. This did the trick.
here's the code in case anybody is interested:
// package, and other code here.
var loadingNewGame:Boolean = new Boolean(false);
function initPlay():void
{
//code here determining what display object to add to the list and assign
//it to the currentLevel variable (a movieclip)
if(userIsLoadingOtherLevel)
{
loadingNewGame = true;
removeEnterFrameListener();
addChild(currentLevel);
}
if(userIsGointToNextLevel)
addChild(currentLevel);
loadingNewGame:Boolean = false;
currentLevel.addEventListener(Event.ENTER_FRAME, onEnterFrame);
function onEnterFrame(event:Event):void
{
if(loadingNewGame)
removeChild(currentLevel);
//collision detection, parallax scrolling, etc, etc is done here.
if(allCoinsCollected)
loadNextLevel();
if(ESCKeyPressed)
ESCKeyPressHandler();
}
function loadNextLevel():void
{
removeChild(currentLevel);
newLevelToLoad++
removeEnterFrameListener();
initPlay();
}
function ESCKeyPressHandler():void
{
initPlay();
}
function removeEnterFrameListener();
{
currentLevel.removeEventListener(Event.ENTER_FRAME,onEnterFrame)
trace("currentLevel.hasEventListener(Event.ENTER_FRAME) = "+currentLevel.hasEventListener(Event.ENTER_FRAME));
//outputs true
}

Why not use a basic approach to registering a click event for a view ( say ButtonView)

Let's say there is a buttonView ( ie. a simple button) . To register a click in my rootView , i do as follows :
generally followed 1st Approach :
class RootView
{
var buttonView:ButtonView ;
....
buttonView.addEventListener( ButtonView.CLICK, buttonView_click ) ;
...
}
I wonder, why not a basic approach ( 2nd Approach ) is followed like this :
class RootView
{
var buttonView:ButtonView ;
buttonView.setFunction( buttonView_click ) ;
}
class ButtonView()
{
public function setFunction( someFunction:Function)
{
viewFunctionArray_Arr.push( someFunction )
}
public function onClick()
{
// remove dispatchEvent from here , instead
for ( var i=0; i< viewFunctionArray_Arr.length; i++)
{
//calling the registered functions of all the views one by one
viewFunctionArray_Arr[i] ( ) ;
}
}
}
If the explanation is regarding "Loose Coupling", then i wonder, how "1st approach" is loose coupling, because afterall in the 1st approach, we are using instance "buttonView" in the views that need it. So rootView needs to know about buttonView. Isn't it ?
When you use addEventListener() Flash player is doing something very similar to your second approach.
One reason I would use addEventListener() instead of passing a reference to a function is to avoid potential memory leaks:
addEventListener() has some optional parameters. The last one specifies to use a weak reference: addEventListener(MouseEvent.CLICK, eventHandler, false, 0, true). Weak references do not prevent an object from getting garbage collected.
There are cases when an object is disposed that you need to clean up things (ie: remove event listeners), and using a weak reference can mitigate some of the clean up tasks.
In regards to loose coupling, I think both approaches are the same. In one case the parent object needs to know that the child view dispatches an event. In the other, the parent needs to know that it should pass an event handling function to the view. These are both loosely coupled in my opinion, there is nothing stopping you from using the child view somewhere else.

addressing a child from another class created by getdefinitionbyname

I have a class "pagecompiler" which does the following:
if(page){
removeChild(page);
}
switch (appModel.currentPage){
case "Programma":
case "Winkelwagen":
case "Films":
case "Contact":
case "Reserveer":
var pageClass:* = getDefinitionByName("be.reynaertvincent.view.pages."+appModel.currentPage+"Page");
page = new pageClass();
addChild( page );
break;
}
so it creates a page depending on the switch, the names are (contactpage, filmspage, contactpage, etc.)
each of these pages extend from a class called "page".
And in "page" class I do the following:
contentBg = new ContentBg();
sidebarBg = new SidebarBg();
addChild(contentBg);
addChild(sidebarBg);
Now what I would like is to apply a tween on contentBg when I do the following in pagecompiler:
if(page){
removeChild(page);
}
but I can't seem to address contentBg from there. I tried:
if(page.contentBg){
tweenlite.to(page.contentBg,blablabla);
//removeChild(page);
}
but it doesn't get recognized. Anyone having any ideas?
I see a number of problems with your code. If you correct them, your problem should be solved:
You should stick to naming conventions: Class names should start with an upper case letter. So it should be Page instead of page. Otherwise, you have a member variable with the same name as a type - and potential compilation errors.
getDefinitionByName() is a costly way of instantiating a class in terms of performance, and since it also isn't type safe, you are dealing with a potential problem. Since you already know which classes you are going to instantiate, why not just make page an instance of Page and extend your switch statement:
private var page:Page;
// some code here
switch (appModel.currentPage){
case "Programma":
page = new ProgrammaPage();
break;
case "Winkelwagen":
page = new WinkelwagenPage();
break;
case "Films":
page = new FilmsPage();
break;
case "Contact":
page = new ContactPage();
break;
case "Reserveer":
page = new ReserveerPage();
break;
}
addChild( page );
If you make page an instance of *, as is implied above, you need to cast to the Page class in order to access its contentBg property. Something like this:
tweenlite.to (Page(page).contentBg, ....);
This last point, however, should already be solved if you follow the first two hints, since the compiler now knows that page is of type Page. It is also a nice example why the lower case naming mentioned above doesn't work, as page(page).contentBg is obviously ambiguous.
In your page class , you could create a public method:
public function removeFromStage():void
{
TweenLite.to ( contentBg , etc... {..... onComplete: remove})
}
//Called when the Tween is complete
private function remove():void
{
parent.removeChild( this );
//may come handy , don't forget to add a listener if you do this :)
dispatchEvent ( new Event ( Event.COMPLETE ) );
}
Then you can call it like this
if(page != null ){
//add Complete event listener if necessary...
page.removeFromStage();
}

checking if child exists

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.

Clearing eventListeners on a FileReference object

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.