Get current class+method name in AS3 - actionscript-3

I wondered if there is a simple way I have have a snippet which traces the name of a method when called. I found className which is half-way there, but not something for the method... a 1-line trace(...) is what I'm after so I avoid typing the method name and leaving myself open to mistakes.
This is for testing the order things happen, when I don't want to step through in the debugger.

If you have compiled your swf with debug information and use the debug version of the player you can take a look at getStackTrace property from the Error object:
Quick example:
public function getCallingInfos():Object{
var tmp:Array=new Error().getStackTrace().split("\n");
tmp=tmp[2].split(" ");
tmp=tmp[1].split("/");
return {namespaceAndClass:tmp[0], method:tmp[1]};
}
var infos:Object=getCallingInfos();
trace(infos.namespaceAndClass, infos.method);

public static function getCurrentClassName(c:Object):String
{
var cString:String = c.toString();
var cSplittedFirst:Array = cString.split('[object ');
var cFirstString:String = String(cSplittedFirst[1]);
var cSplittedLast:Array = cFirstString.split(']');
var cName:String = cSplittedLast.join('');
return cName;
}
Used to check if a certain class is constructed or not.
Usage (here I put the code in the main class):
trace('[DEBUG]: '+ClassData.getCurrentClassName(this)+' constructed.');
trace returns:
[DEBUG]: Main constructed.

Related

Flash loading first external swf loaded

I am making an application to test art from a game I volunteered for. Right now the example I am posting will only touch the armors but the loading process is the same throughout the program. I have a movieclip ready to hold the loaded file but it adds it to the container via the class. It works how it should however my issue is that if you use another file with the same classes then it will default to the first file loaded. Even i use loaderr.unloadAndStop() and remove everything from the stage, it will always load the first file that corresponds to the class I am loading by. Since the armor pieces are loaded by class it makes it a hassle to test multiple changes to an armor file without changing the classes on each export. Here is an example of the code that is being used and I am curious if there is any way that I can improve this. `
public class Test extends MovieClip
{
public var mcChar:Display;
public var btnTest:SimpleButton;
public var btnTest2:SimpleButton;
public var ldr:Loader = new Loader();
public var strSkinLinkage:String;
public var strGender:String;
public function Test()
{
btnTest.addEventListener(MouseEvent.CLICK, TestP);
btnTest2.addEventListener(MouseEvent.CLICK, TestP2);
}
public function TestP(e:MouseEvent)
{
mcChar = new Display();
stage.addChild(mcChar);
mcChar.x = 789.6;
mcChar.y = 604.75;
mcChar.width = 667.15;
mcChar.height = 478.55;
strSkinLinkage = "CNC";
strGender = "M"
this.ldr.load(new URLRequest("CNC.SWF"), new LoaderContext(false, ApplicationDomain.currentDomain));
this.ldr.contentLoaderInfo.addEventListener(Event.COMPLETE, this.onLoadSkinComplete);
}
public function TestP2(e:MouseEvent)
{
mcChar = new Display();
stage.addChild(mcChar);
mcChar.x = 789.6;
mcChar.y = 604.75;
mcChar.width = 667.15;
mcChar.height = 478.55;
strSkinLinkage = "CNC";
strGender = "M"
this.ldr.load(new URLRequest("CNC2.SWF"), new LoaderContext(false, ApplicationDomain.currentDomain));
this.ldr.contentLoaderInfo.addEventListener(Event.COMPLETE, this.onLoadSkinComplete);
}
public function onLoadSkinComplete(e:Event):*
{
var AssetClass:Class;
try
{
AssetClass = (getDefinitionByName(((strSkinLinkage + strGender) + "Head")) as Class);
mcChar.head.addChildAt(new (AssetClass)(), 0);
}
catch(err:Error)
{
AssetClass = (getDefinitionByName(("mcHead" + strGender)) as Class);
mcChar.head.addChildAt(new (AssetClass)(), 0);
};
AssetClass = (getDefinitionByName(((strSkinLinkage + strGender) + "Chest")) as Class);
chest.addChild(ldr.content (AssetClass)());
mcChar.chest.addChild(new (chest)());
this.ldr.contentLoaderInfo.removeEventListener(Event.COMPLETE, this.onLoadSkinComplete);
}
}
`
I don't think its well formatted on this site but this is the core code. I have separate removal functions and my imports are all there. Like I said I cant seem to get it to format correctly. This is my test scenario and isn't my full dynamic tester where I can choose the file. Any help in figuring out how to use the most recent file is appreciated. Also for some background I am more of a self taught novice in as3.
When it gets to loading and unloading assets in AS3, there are several things to learn.
ApplicationDomain is a container for class definitions. The getDefinitionByName(...) method is basically the same as calling the ApplicationDomain.getDefinition(...) on the current ApplicationDomain (or maybe on the main ApplicationDomain, I never tried to do it in the loaded content). As the side result, you cannot have two classes with the same names inside the same ApplicationDomain (or rather you can, but one of them is inaccessible, who knows).
When you load another SWF which falls into the "same domain" category (same www domain, or same/nested local folder), AS3 automatically mixes all the definitions from the loaded SWF into the main ApplicationDomain. If you are willing to have some advanced control over loading/unloading stuff, or/and there are "skin" libraries that have similar sets of classes, you need to put the loaded files into separate ApplicationDomains or their definitions will collide and the result will be unpredictable (yet obviously not satisfactory).
The Loader.load(...) method has a second argument that allows you to do so:
// If there are no mandatory constructor arguments,
// you are free to omit the () brackets. I like doing so.
var aLoader:Loader = new Loader;
var aRequest:URLRequest = new URLRequest("mylibrary.swf");
// Documentation states that passing no argument here is
// the same as passing ApplicationDomain.currentDomain.
var childDomain:ApplicationDomain = new ApplicationDomain;
var aContext:LoaderContext = new LoaderContext(false, childDomain);
aLoader.load(aRequest, aContext);
Thus, when external SWF library is loaded, you can obtain its classes/definitions as following:
var aClass:Class;
// If you get things from the loaded SWF's Library
// then it is Sprite or MovieClip for the most cases.
var anAsset:Sprite;
aClass = aLoader.contentLoaderInfo.applicationDomain.getDefinition("MyAssetClass") as Class;
anAsset = new aClass;
When you do not longer need some of the loaded libraries, you call the Loader.unloadAndStop(...) method on the relevant Loader instance. Combined with the loading SWF into separate ApplicationDomain you can be sure that all of the loaded content (graphics, classes, sounds) is unloaded, destroyed and removed (that one I actually checked):
// Passing "true" also forces the Garbage Collector
// to actually do its job for a change.
aLoader.unloadAndStop(true);

How can I make a function to make a child of an object in the library based on the variables supplied?

I'm trying to do a little project for my class and though I know how to do it the long way I'd prefer to do it in a more intuitive way so that I can avoid having to copy and paste a load of essentially the same code. The idea is to have a function which will create an instance of a class object with it's own unique name, set it's position/size/etc, and then add that child to the stage. Looking at this (what I have now) might help out a little bit.
//Set up variables for all deco pieces
var decoGreen:GreenBall;
var decoRed:RedBall;
var decoStar:Star;
var decoFlower:Flower1;
var decoYellow:YellowBall;
var decoBlue:BlueBall;
//Functions to allow easier object placement
function makeDeco(posX:Number, posY:Number, decoName:String, rootClass:Object):void
{
decoName = new (rootClass)();
decoName.x = posX;
decoName.y = posY;
addChild((decoName));
}
makeDeco(90,320,"greenBall",GreenBall)
Now obviously this code doesn't work and it's pretty rough right now but I think it's sufficient to understand what I'm trying to accomplish here. Thanks for any and all who attempt to decipher my mess! :D
You are pretty close from what I can tell and if I understand your question, it would simply be using the getDefinitionByName class
function makeDeco(posX:Number, posY:Number, decoName:String):void
{
var DecoClass:Class = getDefinitionByName(decoName) as Class;
var deco:DisplayObject = new DecoClass();
deco.x = posX;
deco.y = posY;
addChild((deco));
}
makeDeco(90,320,"greenBall")
You don't need to define the variables initially like you did, granted they've all set to "Export as actionscript" in the library. For example calling a string of "greenBall" would mean you have a movie clip in the library with a class name of greenBall

Value Will Set properly, but Get receives Null

So, I have successfully grabbed a value out of an XML document and set it into a separate class called "AddCommas." The trace functions have shown me that it sets properly.
For more details, my objective is to take the language indicator ("fr" for french or "en" for english), set it inside the appropriate class and into a variable I will use. Now, I am using this variable to be used in an if statement; which will help me format a number properly (commas, decimals, spaces) per the clients request.
However, my problem is when I try to get the value to use it. It always comes back as Null. I have placed traces all over my program trying to pinpoint when this happens, but I cannot find it. Here's the code...
The pull from the XML file and into the set (this works fine, but I am adding it for your benefit in case I missed something)
public var commaHold = new AddCommas();
localLanguage = xmlObj.localLanguage;
trace("localLanguage + " + localLanguage);
commaHold.setLanguage(localLanguage); // Set Language
//More code follows...
This is the set function istelf...
public function setLanguage(localLanguage:String){
langHold = localLanguage;
trace("Set Language = " + langHold); //This always shows a successful set
}
Now am I wrong in thinking that in AS3, once langHold in my AddCommas class has been set I should be able to use it without calling a get within the function I am using the If Statement in, right? Such as this?
var language = langHold;
if (language == "en"){
trace("Language is = " + language); // More code follows afterwards and as of now, this shows NULL
Now, I have attempted plenty of Get functions to add the language variable in the call itself to this function and it's always the same. Am I missing some fundamentals here?
Thank you very much for your time.
If you expect a string comparison you need to use quotes, unless en is a String variable since langHold is a String, like:
if (language == "en"){
Consider modifying the set function to use the as3 keyword like:
private var _language:String;
public function set language(value:String):void {
_language = value;
//do other stuff here if necessary, put a breakpoint on the line above
}
public function get language():String{
return _language;
//put a breakpoint on the line above
}
You should be able to see when any instance of your class has the property changed. The only other issue I can think of is it is not the same instance of the class and therefore doesn't share the property value you set earlier. In the debugger you can check the "hashCode" or "address" it shows for this to see if it changes when it hits the breakpoints.
Here's a sample Singleton structure in AS3 (this all goes in one file):
package com.shaunhusain.singletonExample
{
public class SingletonExample
{
private static var instance:SingletonExample;
public static function getIntance():SingletonExample
{
if( instance == null ) instance = new SingletonExample( new SingletonEnforcer() );
return instance;
}
/**
*
* #param se Blocks creation of new managers instead use static method getInstance
*/
public function SingletonExample(se:SingletonEnforcer)
{
}
}
}
internal class SingletonEnforcer {public function SingletonEnforcer(){}}
using this single shared instance from any other class would look something like this:
private var singletonInstance:SingletonExample = SingletonExample.getInstance();
ShaunHusain's theory of using a Singleton was the perfect solution I needed. However, his code gave me a bizarre 1061 error and my format and code appeared to be error free. Regardless, I looked up another way to use a Singleton as follows that worked perfectly for me. Honestly, Shaun's code should work for anyone and I have no idea why it wasn't. I am perfectly willing to admit that it was probably a typo on my end that I just did not see.
I ended up embedding the Set and Get within the Singletons class and used it as an intermediary to hold the information I needed. It worked perfectly.
package chart {
import chart.*;
//
public class StaticInstance {
private static var instance:StaticInstance;
private static var allowInstantiation:Boolean;
private var language:String;
public static function getInstance():StaticInstance {
if (instance == null) {
allowInstantiation = true;
instance = new StaticInstance();
allowInstantiation = false;
}
return instance;
}
public function StaticInstance():void {
if (!allowInstantiation) {
throw new Error("Error: Instantiation failed: Use StaticInsance.getInstance() instead of new.");
}
}
public function setLanguage(_language:String):void{
language = _language;
trace("language set = " + language);
}
public function getLanguage():String{
return language;
}
}
}
This code allowed me to hold the data and call upon it again from two different classes. It's a very hack job instead of just being able to pass on the variable from function to function, but in my case we didn't create this file, we are modifying it and attempting to do things beyond the original scope of the project.
Thanks again for your help Shaun! I hope this helps other people!

AS3/Flex - persistNavigatorState + objects with ArrayCollection()

I currently have a mobile application on the playbook that has the following class:
[Bindable]
public class Foo
{
public var myString:String;
public var myList:ArrayCollection;
public function Foo() {}
}
I also have persistNavigatorState="true" in my ViewNavigatorApplication.
Suppose in my first view I have the following in my creationComplete="init()" call:
private function init():void {
var s:String = "test_string";
var a:ArrayCollection = new ArrayCollection();
a.addItem("test1");
a.addItem("test2");
a.addItem("test3");
data.foo = new Foo();
data.foo.myString = s;
data.foo.myList = a;
trace(data.foo.myString);
trace(data.foo.myList[0]);
trace(data.foo.myList[1]);
trace(data.foo.myList[2]);
}
When executed, everything works fine in my app. However, since I want the sessions to persist in case the user accidentally closes the app, when he re-opens it the data should still be there.
Instead, when I close and re-open my app only the myString property persists (ie traces "test_string", as intended), however the ArrayCollection isn't copied.
I've tried the following with ObjectUtil.clone() and ObjectUtil.copy():
data.foo.myString = ObjectUtil.copy(s) as String;
data.foo.myList = ObjectUtil.copy(a) as ArrayCollection;
and I've also tried:
var f:Foo = new Foo();
f.myString = s;
f.myList = a;
data.foo = ObjectUtil.copy(f) as Foo;
trace(data.foo.myString);
trace(data.foo.myList[0]);
but this only throws me a
TypeError: Error #1009: Cannot access a property or method of a null object reference.
Any ideas on how to persist ArrayCollections and Foo class in a mobile application?
I'm not 100% sure, but I was wondering about this type of problem while working on a mobile app recently.
I believe your problem might be happening b/c your are setting the data manually on the View, instead of passing it into the View with the ViewNavigator.pushView() method.
I just browsed through the source, and it looks like setting the data directly on the View will bypass ViewNavigator's data persistence. Though with that said, I'm not sure why it would even remember the value for that String :)
I would try to do the following:
don't set the View's data property from inside the view, as you are doing now in the creationComplete handler
if possible, use the "firstView" property of ViewNavigatorApplication in mxml
if possible, initialize the "firstViewData" property in mxml (may not be possible)
if you can't do the two above, in your application's startup code call navigator.pushView(View_Class_Name, foo) to pass in the data.

Can a Flex 3 method detect the calling object?

If I have a method such as:
private function testMethod(param:string):void
{
// Get the object that called this function
}
Inside the testMethod, can I work out what object called us? e.g.
class A
{
doSomething()
{
var b:B = new B();
b.fooBar();
}
}
class B
{
fooBar()
{
// Can I tell that the calling object is type of class A?
}
}
Sorry the answer is no (see edit below). Functions received a special property called arguments and in AS2 it used to have the property caller that would do roughly what you want. Although the arguments object is still available in AS3 the caller property was removed from AS3 (and therefore Flex 3) so there is no direct way you can do what you want. It is also recommeded that you use the [...rest parameter](http://livedocs.adobe.com/flex/3/langref/statements.html#..._(rest)_parameter) language feature instead of arguments.
Here is a reference on the matter (search for callee to find the relevant details).
Edit: Further investigation has shown that it is possible to get a stack trace for the current executing function so if you are lucky you can do something with that. See this blog entry and this forum post for more details.
The basic idea from the blog post is you throw an Error and then catch it immediately and then parse the stack trace. Ugly, but it may work for you.
code from the blog post:
var stackTrace:String;
try { throw new Error(); }
catch (e:Error) { stackTrace = e.getStackTrace(); }
var lines:Array = stackTrace.split("\n");
var isDebug:Boolean = (lines[1] as String).indexOf('[') != -1;
var path:String;
var line:int = -1;
if(isDebug)
{
var regex:RegExp = /at\x20(.+?)\[(.+?)\]/i;
var matches:Array = regex.exec(lines[2]);
path = matches[1];
//file:line = matches[2]
//windows == 2 because of drive:\
line = matches[2].split(':')[2];
}
else
{
path = (lines[2] as String).substring(4);
}
trace(path + (line != -1 ? '[' + line.toString() + ']' : ''));
Is important to know that stackTrace is only available on the debugger version of Flash Player. Sorry! :(
I'd second the idea of explicitly passing a "callingObject" parameter. Unless you're doing really tricky stuff, it should be better for the caller to be able to supply the target object, anyway. (Sorry if this seems obvious, I can't tell what you're trying to accomplish.)
To add to the somewhat ambiguous first paragraph of James: the arguments property is still available inside a Function object, but the caller property has been removed.
Here's a link to the docs: http://livedocs.adobe.com/flex/3/langref/arguments.html
This might help someone, I'm not sure... but if one is using an Event this is possible using the e.currentTarget as follows:
private function button_hover(e:Event):void
{
e.currentTarget.label="Hovering";
}