Friday, May 29, 2009

my replacement for trace()

When debugging a Flash app online, it always sucks that you can't use trace(). There are all sorts of tricks to get around this, including the great DeMonster Debugger.

As much as I like such tools, I've always wished for something simpler. Something like trace() -- but a trace() that works in the browser. Recently, my wish was granted. I learned that Firebug's console accepts logs from Flash. (Firebug is a must-have Firefox plugin for web developers.)

Firebug has a Javascript API which contains a function called console.log. That's great, because Flash can call JS functions, via ExternalInterface.call(functionName,param[s]).

So, all you have to do to send a message from Flash to Firebug is...

1. make sure that the allowScriptAccess parameter is set to "always" in the html page's object and embed tags (it's often set to "sameDomain" by default).

2. In the Actionscript, import Flash.external.ExternalInterface (works in AS2 and AS3).

3. When you want to "trace" to the console, type something like this: ExternalInterface.call( "console.log", "hello, world" );

That's great, but I don't want have to type something that long every time I want to run a quick trace-like action. So I made this little utility function (below). Feel free to use it. Just...

1. Copy it into the folder that contains the fla.

2. Make sure allowScriptAccess is set to "always" in the HTML's object and embed tags.

3. When you want to "trace," just call log(param1, param2, ... paramN);

The cool thing is that log() calls trace() internally, so you can use it for both local development and in-browser testing.

NOTE: with one angry client of mine, the ExternalInterface.call() method cased JavaScript errors on their site. So before going live, I recommend you comment out the final line in the log() function and recompile.

package 
{
    import flash.external.ExternalInterface;

    public function log( message : *, ... rest ) : void
    {
        var s : String = "";
                var a : Array;
                
                a = rest as Array;
                
                a.unshift( message );
        
        trace.apply( this, a );
        
        for ( var i : int = 0; i < a.length; i++ )
        {
                s += a[ i ].toString() + " ";
        }
        
        ExternalInterface.call( "console.log" , s );
    }
}





There are a few of things worth noting about this function: first of all, it's in a package by itself -- it's not part of a class. Normally, I think of that as a no-no, but in this one case, I let myself do it. I want to be able to type just log(), not Debug.log() or something like that. But if the stand-alone function offends you, just pop it into a class.

log() accepts an arbitrary number of parameters, via the relatively obscure structure ...rest. So you can call it like this log("hello"); like this: log("hello", true); like this: log("hello", true, 45.6, [1,2,3], mySprite.x); etc.

In the above examples, "hello" would be stored in the message parameter and the rest of the parameters would be stored in rest. rest is an Array-like object, but it's not actually an Array. That's a problem, because the Function.apply() method, which I use to call trace(), needs an Array. That why I create the a Array and set it to rest as Array(). Then I push message onto the beginning of it, so that all the parameters are in one big Array.

That allows me to call trace.apply( this, a ). The Function.apply method isn't very well known. It allows you to call a function and use an Array as that function's list of parameters. For instance, the following two snippets have the same effect:

SNIPPET 1:
trace(1, 2, 3);

SNIPPET 2:
var a : Array = [1, 2, 3];
trace.apply( this, a );

(The first parameter to apply allows you to apply the function to a specific object. It's not important here, so I just used "this.")

Finally, for Firebug's console, I concatenate all the a values into a space-separated string and output them using the Console.log JS function. If you comment out the last line, log() will still work for local traces but it will no longer try to call a JS function on the web page housing the swf.

1 comment:

  1. We should build you a monument!
    Thank you very much for sharing all this good work.
    Keep up!

    ReplyDelete