How can I copy an ArrayCollection of transfer objects in actionscript? - actionscript-3

public function create():ArrayCollection{
var index:int = 0;
var data:ArrayCollection = new ArrayCollection();
var length:int = originalData.length;
for(index; index < length; index++){
data.addItem(originalData[index]);
}
return data;
}
originalData is the original state of my data from database.
data is a copy of originalData used to be manipulated as the provider for my List component.
There's a button I use to call the create() function above, that
would mean, I want to revert all changes in data, and go back to
everything I have in originalData.
But when I debug my function, originalData has all the changes made in data.
When I use
list.selectedItem.thing = "new string";
is supposed to modify data[index].thing, because data is my List.dataprovider. but it changes originalData[index].thing also and this collection wasn't used for anything, except for creating a copy of itself!
I don't know why this happens. I didn't know how to phrase this behaviour as a google query.
Please, if you don't understand the question, comment it so I can try and make it clearer. This has consumed more time than its functionality is worth.
EDIT:
I've also tried this, but it doesn't work:
public function create():ArrayCollection{
var index:int = 0;
var data:ArrayCollection = new ArrayCollection();
var length:int = originalData.length;
for(index; index < length; index++){
// initializing a Item object.
var dataItem:Item = new Item();
dataItem = originalData[index] as Item;
data.addItem(dataItem);
}
return data;
}
EDIT 2:
Based on your answers and some research I came up with this generic function to copy arrayCollections made of custom objects.
public static function copy(objectClassName:String, objectClass:Class, arrayCollection:ArrayCollection):ArrayCollection{
var index:int = 0;
var length:int = arrayCollection.length;
var copy:ArrayCollection = new ArrayCollection();
for(index; index < length; index++){
registerClassAlias(objectClassName,objectClass);
copy.addItemAt(ObjectUtil.copy(arrayCollection.getItemAt(index)) as objectClass,index);
}
return copy;
}

As Tom says, this is because AS3 passes by reference. If you don't want to modify the original values then you should, again as Tom says, create copies of them.
Fortunately, AS3 has a utility to do this -- ObjectUtils.copy. Try this code instead of your original:
public function create():ArrayCollection{
var index:int = 0;
var data:ArrayCollection = new ArrayCollection();
var length:int = originalData.length;
for(index; index < length; index++){
data.addItem(mx.utils.ObjectUtil.copy(originalData[index]));
}
return data;
}
Do take note that copy() returns a generic Object. If you want to access any of its properties in a type-safe manner you'll have to cast it to its type.
A bit more about the pass-by-reference deal. Let's say we have items a, b, and c floating around in memory. You put them into an array(originalData). originalData now contains references to a, b, and c. You then create an ArrayCollection and insert (again) references to a, b, c. Objects a, b, and c aren't what's being stored in either the array or the ArrayCollection. So when you update originalData[0] you're getting a reference to an object (a), and updating it.
Likewise when you update ArrayCollection.getItemAt(0) you're getting the same reference and updating the underlying object which is why you're getting the behavior you're getting. Making a copy and inserting it means you're referencing an entirely new object.

I am not used to actionscript, but it looks to me like you are putting references to objects from one arrayCollection to another arrayCollection. So if you change those objects, this will be reflected in both arrayCollections since they refer to the same objects.
To avoid this you should make copies from the original objects to put in the new arrayCollection.

To create a deep copy of an array and keep the type, use the following method:
public static function clone(source:Object):*
{
var myBA:ByteArray = new ByteArray();
myBA.writeObject(source);
myBA.position = 0;
return(myBA.readObject());
}
This is the way proposed by Adobe.
Update: idea for a type safe copy function
public static function copyTypeSafe( ac:ArrayCollection ):ArrayCollection
{
var cloneAc:ArrayCollection = new ArrayCollection();
if( ac.length == 0 ) {
return cloneAc;
}
var className:String = getQualifiedClassName( ac.getItemAt(0) );
registerClassAlias( className, (getDefinitionByName( className ) as Class ) );
for each (var obj:Object in ac)
{
cloneAc.addItem( ObjectUtil.copy( obj ) );
}
return cloneAc;
}

You need to do a registerClassAlias call for each of the classes inside of your collection, then just use the function clone with the ByteArray suggested above and it will work fine. The key is to do a registerClassAlias call for each type inside of the ArrayCollection which you want to maintain. In my case:
var productGroupClassName:String = getQualifiedClassName(ProductGroup);
registerClassAlias( productGroupClassName, ProductGroup );
var productClassName:String = getQualifiedClassName(Product);
registerClassAlias( productClassName, Product );
//Need to create a copy so the original values will not be altered
dataProvider = copyTypeSafe(EnalityData.productsInfo);

Related

AS3 - Using a For Loop to Update Multiple Points and Their Values in an Array

I'm a bit new with AS3 (but not really with coding) so please forgive my ignorance. I'm creating a small function that will be called by a Main Function to update the position of 52 Pointers that have the x and y position of multiple point objects (empty movie clips). It will also then update two global arrays with those values (one array for the x and one for the y).
The problem is, as there are 52 of them, and they will probably grow in quantity, I'd like to be able to use a FOR function to do it, but I can't seem to be able to figure it out.
I get this error: Access of undefined property _point.
Here is a piece of the code that dream about:
function happyFunc():void
{
var avpointers:int = 52;
var vpointx:Array = new Array();
var vpointy:Array = new Array();
for (aa=0; aa<vpointers; aa++)
{
vpointx[aa] = _point[aa].x;
vpointy[aa] = _point[aa].y;
}
}
And this is the code that I'm stuck with...
function reallySadFunc():void
{
_point1 = localToGlobal(new Point(point1.x,point1.y));
//...
_point52 = localToGlobal(new Point(point52.x,point1.y));
vpointx[0] = _point1.x;
vpointx[1] = _point2.x;
//...
//oh god there are 104 lines of this why do I have to suffer
}
Thank you!
If I read your question correctly, I think this is what you need:
public static const CLIP_COUNT:int = 52;
// ...
private function happyFunc(parentClip:DisplayObjectContainer):void
{
var vpointx:Vector.<Number> = new Vector.<Number>(CLIP_COUNT, true);
var vpointy:Vector.<Number> = new Vector.<Number>(CLIP_COUNT, true);
var clipPoint:Point = new Point ();
var globalPoint:Point;
for (var i:int = 0; i < CLIP_COUNT; i++)
{
var childClip:DisplayObject = parentClip.getChildByName("point" +
(i + 1).toString());
clipPoint.x = childClip.x;
clipPoint.y = childClip.y;
globalPoint = parentClip.localToGlobal(clipPoint);
vpointx[i] = globalPoint.x;
vpointy[i] = globalPoint.y;
}
// do something with vpointx and vpointy - they're local variables
// and will go out of scope unless you declare them as class members
// or somehow return them from this function.
}
This function works by taking the parent display object (the one that contains the 52 movie clips - this could be the Stage) and iterates through them by getting each movie clip by name. The code assumes that your movie clips are called point1, point2, ..., point52.
To speed up the local-to-global coordinate conversion, two Point objects are created and then reused during the for loop to avoid creating many temporary Point instances.
Instead of using Array, use Vector.<Number> - this class has better performance than Array does.

Actionscript - how to pass object by value?

I want to pass an object to a function by value so that I can make modifications to that object. I don't want the original object to be updated. However, all the function parameters are passed by reference.
I've tried to copy an object ( var new_object:Object = original_object; ) This just creates a pointer to original_object.
Is there a way I can pass parameter by value?
update One workaround I see is to make deep copy of an object by using ByteArray as described here. Not sure how efficient it is. Maybe there is a better solution out there.
You will have to make a copy of the object before passing it to the function :
public function copy(value:Object):Object
{
var buffer:ByteArray = new ByteArray();
buffer.writeObject(value);
buffer.position = 0;
var result:Object = buffer.readObject();
return result;
}
public function testFunction(obj:Object):void
{
//do something with obj
}
public function test():void
{
var obj:Object = {};
testFunction(copy(obj));
}

How to clone a dictionary (and its content)?

I googled some time to find a way to clone a dic, and I found some options, but they didnt work. One was to use a byteArray, it works only if you register all the clases involved and your classes have all their constructor parameters with default values <- not a choice in my case.
Ohter was, a copy but only for the first values, this one:
private function clone(original:Dictionary):Dictionary {
var cloned:Dictionary = new Dictionary();
for(var key:Object in original) {
cloned[key] = original[key];
}
return cloned;
}
But, it doesnt clone a dicionary inside the main dictionary.
Do you know how to make a perfect clone of a given dictionary, even if it has another dictionary inside?
Thanks.
Make a recursive function that checks the elements type, if they are dictionaries themselves keep recursing and copying.
Something like (untested):
private function clone(original:Dictionary):Dictionary {
var cloned:Dictionary = new Dictionary();
for(var key:Object in original) {
if( original[key] is Dictionary)
cloned[key] = clone(original[key]);
else
cloned[key] = original[key];
}
return cloned;
}
Edit :
Seems to work for a simple nested dictionary like this :
var dic:Dictionary = new Dictionary();
dic["test"] = 5;
dic["otherDic"] = new Dictionary();
dic["otherDic"]["foo"] = 1;
dic["otherDic"]["bar"] = "bar";
var copy:Dictionary = clone(dic);
dic["test"] = 0;
dic["otherDic"]["foo"] = 0;
dic["otherDic"]["bar"] = "changed";
//copy doesn't change, copy and dic are different dictionaries

ActionScript - Get Instance Name From Constructor Without Passing Parameters?

is it possible to obtain the instance name of a class from the class without having to manually pass the instance name as a string parameter to the class constructor?
//Create New SizeClass
var big:SizeClass = new SizeClass();
//-------------
package
{
public class SizeClass
{
public function SizeClass()
{
trace( //-- Instance Name "big" --// );
}
}
}
No, it is not possible to know anything about the containing code block during a constructor, save what you can learn from the stack trace (though that's not available except in the debugger version of Flash). Even if you had a global access point for the containing class, it still would not allow for that access.
Think of a constructor like a method call. In a line of AS, it will be called before the assignment. Eg: var a:Foo = new Foo() the Foo is created (the constructor completes), and then a is populated with whatever just happened. After that point a will remain agnostic of its context (because of encapsulation) unless it is told about it (this is even true on a DisplayObject -- try this( var mc:MovieClip = new MovieClip(); trace( mc.root ) //this will be null ).
I'm keeping this because it is useful albeit not useful to your original answer.
You can always get the name of a class with getQualifiedClassName from the flash.utils package. You can't get a DisplayObject's until well after it has been constructed, but you can simulate this by (I believe) overriding function set name( value:String ):void. If that doesn't work, then try finding it after Event.ADDED and/or Event.ADDED_TO_SAGE.
The instance name isn't very important. You'd better store references of the instances inside an array.
var sizes:Array = new Array();
var big:SizeClass = new SizeClass();
sizes.push( big );
When you want to access them, you can loop through the array.
for (var i:uint = 0; i < list.length; ++i)
{
var size:SizeClass = list[i] as SizeClass;
trace( size );
}
BTW: Instead of an instance name it is possible to add an automatic index to your class.
package
{
public class SizeClass
{
private static var global_index:int = 0;
public const INDEX:int = global_index ++;
}
}
Which you can access like this:
var big:SizeClass = new SizeClass();
trace(big.INDEX) // 0
var small:SizeClass = new SizeClass();
trace(small.INDEX)// 1
source: http://blog.stroep.nl/2010/08/auto-increment-as3-class/

Add multiple movieclips, not replacing the old ones

So, in short, my problem is this. I am using a variable which is a movieclip loaded from an external swf. I want to "spawn" multiple instances of the movieclip that all react to the same code, so for example if I say var1.x = 100, they all are at 100x. But my problem is when I run addChild(var1) multiple times(I'm not actually typing in addChild(var1) over and over, I just have it set to add them at random times), the new child just replaces the old one, instead of making multiple movieclips. Should I do something like
var var1:MovieClip
var var2:MovieClip = new var1 ?(which doesnt work for me btw, gives me errors)
Oh, heres the code, and also, I am pretty new to as3 fyi, still don't even know how arrays work, which was my second guess to the problem.
var zombieExt:MovieClip;
var ldr2:Loader = new Loader();
ldr2.contentLoaderInfo.addEventListener(Event.COMPLETE, swfLoaded2);
ldr2.load(new URLRequest("ZombieSource.swf"));
function swfLoaded2(event:Event):void
{
zombieExt = MovieClip(ldr2.contentLoaderInfo.content);
ldr2.contentLoaderInfo.removeEventListener(Event.COMPLETE, swfLoaded2);
//zombieExt.addEventListener(Event.ENTER_FRAME, moveZombie)
zombieExt.addEventListener(Event.ENTER_FRAME,rotate2);
function rotate2 (event:Event)
{
var the2X:int = playerExt.x - zombieExt.x;
var the2Y:int = (playerExt.y - zombieExt.y) * 1;
var angle = Math.atan(the2Y/the2X)/(Math.PI/180);
if (the2X<0) {
angle += 180;
}
if (the2X>=0 && the2Y<0) {
angle += 360;
}
//angletext.text = angle;
zombieExt.rotation = (angle*1) + 90;
}
playerExt.addEventListener(Event.ENTER_FRAME,spawn1);
function spawn1 (event:Event)
{
if(playerExt.y < 417)
{
var someNum:Number = Math.round(Math.random()*20);
if(someNum == 20)
{
addChild(zombieExt)
zombieExt.x = Math.round(Math.random()*100)
zombieExt.y = Math.round(Math.random()*100)
}
}
}
}
addChild() does not create new instances. It is used to add an already created instance to the display list. If you call addChild() multiple times on the same instance then you are just readding itself.
Also each instance is unique, you can not globally change the x position of an instance by changing another one of them. What you would do is as Henry suggests and add each new instance of a MovieClip into an array, then whenever you change something you can loop through the array and apply the changes to each instance.
You can not go var2:MovieClip = new var1 either since var1 is an instance and not a class.
Here's a different method of receiving loaded MovieClips, which i use when i need many copies of the item.
in the swf you are loading, give the target movieclip a linkage name in the library, for this example i will use "foo"
private var loadedSwfClass:Class
private var newZombie:MovieClip;
private var zombieArray:Array = new Array();
function swfLoaded2(event:Event):void
{
loadedSwfClass = event.target.applicationDomain.getDefinition("foo");
for(var n:int = 0; n<100; n++){
newZombie = new loadedSwfClass()
zombieArray.push(newZombie);
addChild(newZombie);
}
}
as per this tutorial
http://darylteo.com/blog/2007/11/16/abstracting-assets-from-actionscript-in-as30-asset-libraries/
although the comments say that
var dClip:MovieClip = this;
var new_mc = new dClip.constructor();
this.addChild(new_mc);
will also work.
It sounds like you might be accessing the same instance some how in your code. It would be helpful to see your code to figure this one out.
If I wanted to load in one swf files and add a MovieClip multiple times I would place it in the library of that SWF file. And then instantiate it and store it into an object pool or a hash or some list.
// after the library were finished loading
var list:Array = [];
for(var i:int=0; i<10; i++) {
var myCreation:MySpecialThing = new MySpecialThing();
addChild(myCreation);
list.push(myCreation);
}
where my library would contain a linkage to the class MySpecialThing.
Calling addChild(var1) multiple times on the same parent doesn't have any effect (unless you have added another child to the same parent in between, in which case it will change the child index and bring var1 to the top). If you call it on different parents, it will just change the parent of var1, doesn't duplicate. Call addChild(new MovieClassName()) at random times instead to add new copies of it. Use an array as suggested here to access them later.
Wow, thanks there henry, just using an array did exactly what I needed, and made things alot simpler.
when you load in using a loader you only get 1 instance, however you can do some funky reflection to determine what class type the given loader.content is, and then instantiate them using that. For Example:
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loader_completeHandler);
loader.load(new URLRequest("ZombieSource.swf"));
var classType:Class;
function loader_completeHandler(evt:Event):void
{
var loadInfo:LoaderInfo = (evt.target as LoaderInfo);
var loadedInstance:DisplayObject = loadInfo.content;
// getQualifiedClassName() is a top-level function, like trace()
var nameStr:String = getQualifiedClassName(loadedInstance);
if( loadInfo.applicationDomain.hasDefinition(nameStr) )
{
classType = loadInfo.applicationDomain.getDefinition(nameStr) as Class;
init();
}
else
{
//could not extract the class
}
}
function init():void
{
// to make a new instance of the ZombieMovie object, you create it
// directly from the classType variable
var i:int = 0;
while(i < 10)
{
var newZombie:DisplayObject = new classType();
// your code here
newZombie.x = stage.stageWidth * Math.random();
newZombie.x = stage.stageHeight * Math.random();
i++;
}
}
Any problems let me know, hope this helps.