AS3: How to know if dataprovider or its content(s) is changed - actionscript-3

I'm implementing some kind of combobox control (by extending spark.components.supportClasses.DropDownListBase)
Now, inside this control; I need to know:
if the dataprovider is changed/assigned. (which I can do... the first approach below works);
if any item in the dataprovider collection has changed.
I tried 2 methods that did not do the trick...
1ST APPROACH:
[Bindable("collectionChange")]
override public function set dataProvider(value:IList):void
{
if (value) value.addEventListener(CollectionEvent.COLLECTION_CHANGE, onDataChange);
super.dataProvider = value;
trace("DATA CHANGED"); //fires
}
protected function onDataChange(event:CollectionEvent):void
{
trace("COLLECTION ITEM(S) CHANGED"); //does not fire
}
2ND APPROACH:
Since this is based on DropDownListBase; it should dispatch the CollectionEvent.COLLECTION_CHANGE event already..?
public function myClass() //constructor
{
addEventListener(CollectionEvent.COLLECTION_CHANGE, onDataChange);
}
protected function onDataChange(event:CollectionEvent):void
{
trace("DATA CHANGED"); //does not fire
}
Any ideas?
UPDATE: Edited above.. The first approach lets me know if the dataprovider is changed but not if any item is updated in the dataprovider collection. The second approach does not work at all..

We'll be able to help significantly more if you show us a runnable sample to demonstrate the problem.
if the dataprovider is
changed/assigned.
Your first approach should work. Can you tell us what makes you think it didn't? ( No trace statement I assume? ). And tell us what you did to change the dataProvider.
The second approach won't work because myClass is not firing off the collectionChange event.
2 if any item in the dataprovider collection has changed.
There is not really a way to tell this. In most cases, a collection is just a list of pointers to other objects. If you change those pointers, then a collectionChange event is fired. IF you change the item that he pointer is pointed to, the collection has no way to know that something changed. Binding works very similarly if your an MXML fan.
If you have control over how items are changed, you can deal with it that way. Instead of:
(collection.getITemAt(x) as myObject).property = newValue;
Do something like this:
var myObject : MyObject = collection.getITemAt(x) as myObject
myObject.property = newValue;
collection.setItemAt(x, myObject);
I would expect that to fire a collectionChange event, but not the former.
That said, in the context of a dropDownListBase: As you scroll or open and close the drop down, the itemRenderers should be updated to reflect the most current dataProvider's data. But if you change something on the fly while the drop down is open, I would not expect it to update automatically [if you're not changing the dataProvider.

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
}

Flex 4.6 Mobile - ItemRenderers and application data

A question about ItemRenderers: let's say I have an ArrayCollection that is my application data sitting inside a global object. I them populate a sparks list with this data, setting the ArrayCollection as the dataProvider.
So each ItemRenderer gets a copy of an item sitting in the array. You can override the "set data" method to set the data something more domain-specific. The problem is that the data is a copy of the original item.
Now let's say we want to add some data to the item while inside the ItemRender. For example, it could call a method on the item telling it to load some details about itself, or maybe we allow the user to modify something on the item.
Obviously, we can't do any of this if we are operating on a copy because it will be thrown away as soon as the ItemRenderer is destroyed and the original object doesn't know anything about what happened.
So what's the best practice? Should I just use the itemIndex of the renderer to pull out the original item from my global array like this:
{globalArrayCollection}.getItemAt(this.itemIndex)
But it seems kind of clunky to me. Is there a best practice for dealing with this?
Not sure I'm following but it sounds like you're looking for a way to get at your item renderer to set/change a value.
You could go about accessing a method on the renderer directly.
In your renderer:
public function setSomeValue(aValue:String):void{
someString = aValue;
}
You would also set the data on your ArrayCollection as well.
To access this method you would use this:
var dataGroup:DataGroup = list.dataGroup;
var itemRenderer:YourItemRenderer = dataGroup.getElementAt(list.selectedIndex) as YourItemRenderer;
itemRenderer.setSomeValue("string");
Hmm, why do you think that original ArrayCollection won't change if you change values in itemRenderer? For me this works and initial ArrayCollection changes.
[Bindable]
protected var model:Model;
override public function set data(value:Object):void
{
super.data = value;
this.model = value as Model;
}
protected function changeValue():void
{
model.value = "newValue";
}
Or am I misunderstood something?

Make Flex TextInput show no prompt on empty String

I am using a s:TextInput in Flex 4.5. It shows it's prompt text if the underlying text value is null or empty String. Does anybody know if I can make either don't show the prompt on empty String or even show a different prompt?
I already found a way by extending the TextInput class and overriding some of the methods but I am still hoping anyone here knows an easier way ;-)
Ok, so based on the comments, here it is:
You store the current prompt value in a private variable, like so:
private var _inputPrompt:String = "";
Then you create a getter, so the value is accessible from outside of this class:
public function get inputPrompt():String
{
return _inputPrompt;
}
Now you can bind inputPrompt anywhere you need it, however, the problem is the getter won't be recalled once the private value changes. You can fix this very easily: Create an update method, for example like so:
public function updateInputPrompt(value:String):void
{
_inputPrompt = value;
}
Ok, nothing fancy so far. I'm guessing this is the point where you are right now. In order to "force" the getter to be recalled, you have to bind it to an event, like so:
[Bindable(event="inputPromptUpdated")]
public function get inputPrompt():String
{
return _inputPrompt;
}
Finally, you can simply dispatch this event when the value is update (i.e. in the updateInputPrompt method):
public function updateInputPrompt(value:String):void
{
_inputPrompt = value;
dispatchEvent("inputPromptUpdated"); // For binding
}
This way, the getter will be recalled every time you dispatch that event.
Hope this helps. Have a great day, and a great weekend!

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.