AS3 for Facebook - why is my login callback not being called? - actionscript-3

We're developing a Flash site that integrates with Facebook heavily.
We've noticed a bug where, if a user is not logged into Facebook, and we try to log them in via Facebook.login in the AS3 classes provided by Adobe, the callback is invoked, but the parameters result:Object and fail:Object are BOTH null. If our user then tries to login again, it all works fine, returning the userid in result:Object if successful.
Is this a bug with Facebook itself? Has anyone seen this before? We're sure this wasn't happening about a week ago.
Facebook.login(onLogin, {perms:Config.FB_PERMISSIONS});
private function onLogin(result:Object, fail:Object):void {
// first time this is called after the popup closes returns
// result == null and fail == null
// second time around, results are as expected
}
Cheers,
Mark.

this is a way to connect to Facebook using Graph API. Everything is explained in comment. This is actually the connecting to facebook, no posting to walls or anything. That part can be found below this class.
package com.DAL
{
import com.facebook.graph.Facebook;
import flash.events.Event;
import com.facebook.graph.data.FacebookSession;
import flash.events.EventDispatcher;
import flash.events.MouseEvent;
import com.fbLoginButton;
import com.adobe.serialization.json.JSON;
public class FBConnect extends EventDispatcher
{
/******************************************
* Variables
******************************************/
private var _applicationID:String;
private var _extendedPermissions:Object;
/******************************************
* Constants
******************************************/
public static const CONNECTED:String = "CONNECTED";
/******************************************
* Properties
******************************************/
public function get applicationID():String
{
return _applicationID;
}
/******************************************
* Constructor
******************************************/
public function FBConnect()
{
super();
//Set applicationid
_applicationID = "YOUR_ID";
//Set permissions to ask for
_extendedPermissions = {perms:"read_stream, publish_stream, user_about_me, read_friendlists, user_photos"};
//Initialize facebook
Facebook.init(_applicationID);
}
/******************************************
* Methods
******************************************/
public function login(e:MouseEvent):void
{
Facebook.login(handleLogin, _extendedPermissions);
}
private function handleLogin(response:Object, fail:Object):void
{
dispatchEvent(new Event(CONNECTED));
}
}
}
That should take care of connecting to facebook. If you want to post to walls or anything, you can find a small example below.
/******************************************
* Constructor
******************************************/
public function FBLogic()
{
super();
_connect = new FBConnect();
_connect.addEventListener(FBConnect.CONNECTED, startLoaders);
initLoaders();
}
/******************************************
* Methods
******************************************/
...
public function post(message:String):void
{
var _params:Object = new Object();
_params.access_token = Facebook.getSession().accessToken;
_params.message = message;
Facebook.api("/" + _userID + "/feed", messagePosted, _params, "POST");
}
public function messagePosted(response:Object, fail:Object):void
{
dispatchEvent(new Event(MESSAGEPOSTED));
}
public function login(e:MouseEvent):void
{
var _loginButton:fbLoginButton = e.target as fbLoginButton;
_loginButton.alpha = 0;
_loginButton.visible = false;
_connect.login(e);
}
If this doesn't do the trick you might have forgotten to add some code to your html file. Be sure to add the following code to the head of your html file:
<script type="text/javascript" src="http://connect.facebook.net/en_US/all.js"></script>
And you also need a div called fb-root, declared like this.
<body>
<div id="fb-root"></div>
<div id="flashContent">
</div>
</body>

This is kinda dirty, but for me a timeout before calling login worked fine:
setTimeout(Facebook.login, 200, onLogin, {perms:Config.FB_PERMISSIONS});

_extendedPermissions = {perms:"read_stream, publish_stream, user_about_me, read_friendlists, user_photos"};
Blockquote
"perms" is now "scope"
{scope:"read_stream, publish_stream, user_about_me, read_friendlists, user_photos"};
This is not very well documented on the Adobe Facebook API page.

SHORT ANSWER
Just keep Facebook.init() looping every 0.5 seconds until it finds a valid credential cookie.
LONG ANSWER
The docs describe Facebook.init() as a setup function. But it does more than that, it looks for valid fb logon cookie. As does Facebook.login().
The idea is call Facebook.init() at startup and see if user already have access. Else use Facebook.login() to make the user logon.
Not executable code:
/**
* Start the app, and see if there are any valid credentials
**/
public function startApp() {
Facebook.init(fbAppid, handleFbLogin);
}
/**
* This is a method to call the app didnt login successfully
* at startup.
**/
private function tryLogin() {
Facebook.login(handleFbLogin, {});
}
private var lastLoginSuccess:Object; //being a bit pedantic maybe
/**
* This is the callback that is <i>supposed</i> to run
* ones the user has logged in.
* Unfortunately the callback assoc. with the login/init
* goes off almost instantly; and so often fails before
* valid credentials have been creations >:(
**/
public function handleFbLogin(success:Object, fail:Object) {
lastLoginSuccess = success;
if(success){
//yay!
} else {
//taps foot impatiently...
flash.utils.setTimeout(tryFbInitIfLastFailed, 500);
}
}
/**
* And also the user could be taking their sweet time
* typing in their password.
* Oh and giving their device informative names like 'qwe'
*
* So when they finally login, and the regularly executing
* handleFbLogin() will finally find some valid credentials
**/
private function tryFbInitIfLastFailed() {
if (lastLoginSuccess == null)
Facebook.init(view.fbApp.id, handleFbLogin);
}
Any why not keep calling Facebook.login()? because the opens the Facebook login popup - so you will have a steady stream of popups.

This works for me, either if im not logged in or if im already:
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
Facebook.init("APPID", onInit);
}
private function onInit(response:Object, fail:Object):void
{
if(response)
{
handleLoginResponse(true, null);
}
else
{
Facebook.login(handleLoginResponse, { scope: "email, user_photos, publish_stream" } );
}
}
private function handleLoginResponse(result:Object, fail:Object):void
{
if (result != null) {
// Logged in
} else {
// Goodbye :(
}
}

Add https for all.js in the tag you use in the html file

Related

Flash kiosk touch app - if nothing happens go to homepage

I'm making a simple touchscreen app with flash. When users doesn't use it for 1 or 2 minutes I would like that the movie will automatically go back to the homepage link. How to reach that?
Is there any actionscript that if a mouse doesn't move for 2 minutes the movie will go to a link or it will rewind?
Thank you!
SOLVED:
AS2.0
// main timeline code
timeout = 5000;
this.onMouseMove = function(){
clearInterval(ID);
ID = setInterval(action, timeout);
trace("interaction");
};
function action(){
clearInterval(ID);
trace("no interaction for 5 seconds");
// go to end frame
};
Here is static class I wrote for a kiosk application I made many years ago. You just pass in a reference to the main stage, a function to reset the program, and a dialog box function (you could easily take out that part of the code if you don't want the dialog).
TimeoutManager.init(stage, myResetFunction, myDialogBoxFunction);
You could enable or disable it anytime with: TimeoutManager.kioskTimeoutEnabled = false;
Let me know if you have any questions about it, it's fairly well commented.
package
{
import flash.display.Stage;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.TimerEvent;
import flash.utils.clearTimeout;
import flash.utils.setTimeout;
import flash.utils.Timer;
/**
* Manages the Kiosk session timeout functionality
* #LDMS
*/
public class TimeoutManager
{
//KioskTimeout Vars
private static var kioskTimeoutEnabled_ :Boolean = false;
private static var kioskTimeoutTimer :Timer = new Timer(3000, 1);
private static var kioskTimeout :uint;
private static var timeoutCountdownTimer:Timer = new Timer(1000);
private static var stage :Stage;
public static var defaultTime :int = 120000; //2 minutes - amount of inactivity before the "are you still there" message comes up.
public static var dialogTime :int = 30000; //30 seconds - amount of time to wait for the user to click the "are you still there" message before reseting the applicaiton
public static var resetFunction :Function; //the function to call to reset the application
public static var dialogFunction:Function; //the function to call to show a dialog box - passes tittle & text as two parameters to the method.
public static function init(mainStage:Stage, resetFunction_:Function, dialogFunction_:Function):void {
stage = mainStage;
resetFunction = resetFunction_;
dialogFunction = dialogFunction_;
kioskTimeoutTimer.addEventListener(TimerEvent.TIMER, timeoutKiosk, false, 0, true);
kioskTimeoutEnabled = true;
timeoutCountdownTimer.addEventListener(TimerEvent.TIMER, countdownTick, false, 0, true);
}
/**
* Enable or Disable the Kiosk reset timeout feature
*/
public static function set kioskTimeoutEnabled(val:Boolean):void
{
kioskTimeoutEnabled_ = val;
if (val) {
setKioskTimeout();
try { stage.addEventListener(MouseEvent.MOUSE_DOWN, kioskTimeoutReset, false, 0, true); } catch (e:Error) { trace(e.toString()); };
} else {
cancelKioskTimeout();
try { stage.removeEventListener(MouseEvent.MOUSE_DOWN, kioskTimeoutReset, false); } catch (e:Error) { trace(e.toString()); };
};
}
public static function get kioskTimeoutEnabled():Boolean { return kioskTimeoutEnabled_; };
/**
* Resets the timeout period for the kiosk reset timeout feature - called on a global mouse_down event.
* #param e - unused mouse_down event
*/
public static function kioskTimeoutReset(e:Event = null):void {
cancelKioskTimeout();
setKioskTimeout();
}
/**
* used to cancel the kiosk timeout period temporarily
*/
public static function cancelKioskTimeout():void {
kioskTimeoutTimer.reset();
}
/**
* Set or reset the time (in milliseconds) before the kiosk times out.
* #param overrideTime - if not 0 (default), sets a temporary new time (in milliseconds) before the kiosk times out.
*/
public static function setKioskTimeout(overrideTime:Number = 0):void {
if(!kioskTimeoutEnabled) return;
try { cancelKioskTimeout(); } catch (e:Error) { };
var time:Number = (overrideTime > 0) ? overrideTime : defaultTime;
kioskTimeoutTimer.delay = time;
kioskTimeoutTimer.start();
}
/**
* after the set inactivity period passes, this function is called to let the user know the kiosk is about to reset giving them a chance to keep their session alive
*/
private static function timeoutKiosk(e:Event = null):void {
dialogFunction("Are you still there?", "Reset in " + String(Preferences.kioskTimeout_dialogTime / 1000) + " seconds");
timeoutCountdownTimer.start();
kioskTimeout = setTimeout(goReset, dialogTime);
stage.addEventListener(MouseEvent.MOUSE_DOWN, clearKioskTimeout2,false,0,true);
}
protected static function countdownTick(e:TimerEvent):void {
dialogFunction("Are you still there?", "Reset in " + String((Preferences.kioskTimeout_dialogTime / 1000) - timeoutCountdownTimer.currentCount) + " seconds");
}
/**
* Cancels the timeout prompt timeout
* #param e - unused mouse event
*/
private static function clearKioskTimeout2(e:Event = null):void {
try {
stage.removeEventListener(MouseEvent.MOUSE_DOWN, clearKioskTimeout2,false);
}catch (e:Error) { trace(e.toString()); };
clearTimeout(kioskTimeout);
timeoutCountdownTimer.reset();
}
private static function goReset(e:Event = null):void {
timeoutCountdownTimer.reset();
resetFunction();
}
}
}

Best way to get a variable from another class in AS3

I wonder which one of these methods is the best when trying get a variable from anotherClass to the Main-document-class, and why? Are there any more, even better ways?
In example no 1, and the Main-function, can the if-else statement be triggered before the checkLogin function was completely done? Thanks!
My no 1. way
public class Main extends MovieClip {
// Declaring other classes.
private var checkLogin:CheckLogin;
public function Main() {
checkLogin = new CheckLogin();
if(checkLogin.isLoggedIn == true) {
trace("Is logged in");
} else {
trace("Not logged in");
}
}
}
and
public class CheckLogin {
public var isLoggedIn:Boolean;
public function CheckLogin() {
isLoggedIn = false;
}
}
Or is it a lot better to do it this way,
(Way no 2):
public class Main extends MovieClip {
// Declaring other classes.
private var checkLogin:CheckLogin;
public function Main() {
checkLogin = new CheckLogin();
checkLogin.addEventListener("checkLogin.go.ready", checkLoginReady);
checkLogin.go();
}
public function checkLoginReady(event = null) {
if(checkLogin.isLoggedIn == true) {
trace("Is logged in");
} else {
trace("Not logged in");
}
}
}
and
public class CheckLogin extends EventDispatcher {
public var isLoggedIn:Boolean;
public function CheckLogin() {
isLoggedIn = true;
}
public function go() {
this.dispatchEvent(new Event("checkLogin.go.ready"));
}
}
Generally, using events will make your code easier to maintain.
The login check you describe seems to be instant, so approach number 1 would be ok for such a simple case.
Often the login check (or whatever process) may not be instant, for example if the CheckLogin class sent a server request and waited for the response. In this case using events is better. The Main class can listen for a LOGIN_SUCCESS or LOGIN_FAIL event and handle it when the event occurs. All the login logic stays within CheckLogin.
Also, using events will allow multiple interested classes to react, without them knowing any of the inside login process.

Actionscript 3 - checking for an internet connection

I am using this code in my flash file
import air.net.URLMonitor;
import flash.net.URLRequest;
import flash.events.StatusEvent;
var monitor:URLMonitor;
function checkInternetConnection(e:Event = null):void
{
var url:URLRequest = new URLRequest("http://www.google.com");
url.method = "HEAD";
monitor = new URLMonitor(url);
monitor.pollInterval = 1000;
//
monitor.addEventListener(StatusEvent.STATUS, checkHTTP);
//
function checkHTTP(e:Event = null):void
{
if (monitor.available) {
navigateToURL(new URLRequest("flickr.html"),"_top");
} else {
gotoAndPlay(1);
}
}
monitor.start();
}
I am trying to get the flash to check for a connection and navigate to another page if not it will replay.
It doesnt seem to be working.
Am i missing anything?
I have add the library path to aircore.swc also.
Its meant to be a html page with flash rather than an AIR app
Cheers
(My reputation is too low to comment on Tianzhen Lin's answer directly ...)
I needed to make a few changes in order to get Tianzhen Lin's answer to work as expected:
Added:
urlRequest.useCache = false;
urlRequest.cacheResponse = false;
This addition was required because even when the connection was definitely lost, the check was still succeeding because the cache was being read from.
Changed:
if( textReceived.indexOf( _contentToCheck ) )
to:
if( !(textReceived.indexOf( _contentToCheck ) == -1) )
This change was required because while "" (an empty string) was always being found, it was being found at index '0' which was causing the original if() condition to always fail.
Added:
urlRequest.idleTimeout = 10*1000;
In the case where the network cable was physically disconnected from the router, the request could take a very long time to time-out (Honestly, I got tired of waiting after a couple of minutes.)
After making the above changes, I found Tianzhen's code worked perfectly: No matter how I went about disconnecting/reconnecting Wi-Fi (on both iOS and Android devices) the change in connection status was always detected.
I see two issues in your code. One is that while you have the logic to check internet connection, there is not any code calling the function, therefore the logic of redirection would not be called. Second is that using AIRcore.swc would be a bad idea as you have injected a dependency that might not work with or violate browser sandbox.
You may try the following approach which is tested and requires no AIR's SWC:
Step 1, include a new class ConnectionChecker as follow:
package
{
import flash.events.*;
import flash.net.*;
[Event(name="error", type="flash.events.Event")]
[Event(name="success", type="flash.events.Event")]
public class ConnectionChecker extends EventDispatcher
{
public static const EVENT_SUCCESS:String = "success";
public static const EVENT_ERROR:String = "error";
// Though google.com might be an idea, it is generally a better practice
// to use a url with known content, such as http://foo.com/bar/mytext.txt
// By doing so, known content can also be verified.
// This would make the checking more reliable as the wireless hotspot sign-in
// page would negatively intefere the result.
private var _urlToCheck:String = "http://www.google.com";
// empty string so it would always be postive
private var _contentToCheck:String = "";
public function ConnectionChecker()
{
super();
}
public function check():void
{
var urlRequest:URLRequest = new URLRequest(_urlToCheck);
var loader:URLLoader = new URLLoader();
loader.dataFormat = URLLoaderDataFormat.TEXT;
loader.addEventListener(Event.COMPLETE, loader_complete);
loader.addEventListener(IOErrorEvent.IO_ERROR, loader_error);
try
{
loader.load(urlRequest);
}
catch ( e:Error )
{
dispatchErrorEvent();
}
}
private function loader_complete(event:Event):void
{
var loader:URLLoader = URLLoader( event.target );
var textReceived:String = loader.data as String;
if ( textReceived )
{
if ( textReceived.indexOf( _contentToCheck ) )
{
dispatchSuccessEvent();
}
else
{
dispatchErrorEvent();
}
}
else
{
dispatchErrorEvent();
}
}
private function loader_error(event:IOErrorEvent):void
{
dispatchErrorEvent();
}
private function dispatchSuccessEvent():void
{
dispatchEvent( new Event( EVENT_SUCCESS ) );
}
private function dispatchErrorEvent():void
{
dispatchEvent( new Event( EVENT_ERROR ) );
}
}
}
Step 2, in your main application or anywhere that internet connection should be checked, use the following snippet:
var checker:ConnectionChecker = new ConnectionChecker();
checker.addEventListener(ConnectionChecker.EVENT_SUCCESS, checker_success);
checker.addEventListener(ConnectionChecker.EVENT_ERROR, checker_error);
checker.check();
private function checker_success(event:Event):void
{
// There is internet connection
}
private function checker_error(event:Event):void
{
// There is no internet connection
}
The air.net.URLMonitor is the AIR available class - so will not work outside of the AIR player.
But you could try to do your own as all this monitor class does is "ping" the url and check the response, so you could do similar try to load something from known source like google.com and if it completes wihtout error then it is OK otherwise you will get error.

How do I make my public static logging method write to an inputfield?

I figured out how to create a static method that is available everywhere, for example:
UtilLib.as:
package
{
public final class UtilLib
{
public static function getTimeStamp():uint
{
var now:Date = new Date();
return now.getTime();
}
}
}
I can access this everywhere by doing UtilLib.getTimeStamp() - Now, I want to create a new staic method called log(msg:String). This should log a message to a multi-line inputfield.
The problem however is that this inputfield must be created somewhere and must be accessible and visible all the time, and I don't want to pass it through the function parameters all the time as this would cause a lot of trouble (I'd have to pass it through objects aswell..).
So, how do I make a "public textfield" so my static log method can write to it?
UPDATE:
I now tried the following class, with a static constructor (I think). However, the textfield object is not showing. When I do an addChild(debugField) after creating it, it gives me error 1180.
Logger.as
package
{
import flash.display.Sprite;
import flash.text.TextField;
import flash.text.TextFieldType;
public class Logger extends Sprite
{
public static var debugField:TextField;
/* static block */
{
trace("Logger initializing.");
debugField = new TextField();
debugField.width = 500;
debugField.height = 100;
debugField.x = 100;
debugField.y = 400;
debugField.background = true;
debugField.backgroundColor = 0xFFFFFF;
debugField.defaultTextFormat = new CustomTextFormat();
debugField.mouseWheelEnabled = true;
debugField.multiline = true;
debugField.type = TextFieldType.DYNAMIC;
}
public static function log(msg:String):void
{
if (debugField) debugField.appendText(msg);
}
}
}
I initialize it like this:
var test:Logger = new Logger();
addChild(test);
And I log a new message like this:
Logger.log("test");
Unfortunately, the textField is not showing.
Essentially you need:
somewhere to log a message which is globally accessible
the ability to update a text field whenever the log message changes
A simple solution using objects could look like this:
Example
package {
import flash.display.Sprite;
import flash.text.TextField;
import flash.events.Event;
public class Example extends Sprite {
private var messageLog:TextField;
public function Example() {
createTextField();
MessageLogger.getInstance().addEventListener( MessageLogger.LOG, handleMessageLoggerUpdate );
MessageLogger.getInstance().log( "Message!" );
}
private function handleMessageLoggerUpdate( event:Event ):void {
messageLog.text = MessageLogger.getInstance().getLog();
}
private function createTextField():void {
messageLog = new TextField();
addChild( messageLog );
}
}
}
MessageLogger
package {
import flash.events.EventDispatcher;
import flash.events.Event;
public class MessageLogger extends EventDispatcher {
private static var instance:MessageLogger;
public static function getInstance():MessageLogger {
if ( !instance ) {
instance = new MessageLogger( new InstanceKey() );
}
return instance;
}
public static var LOG:String = "MessageLoader#log";
private var messageLog:String;
public function MessageLogger(key:InstanceKey) {
messageLog = "";
}
public function log( message:String ):void {
messageLog += message;
notify();
}
public function getLog():String {
return messageLog;
}
private function notify():void {
dispatchEvent( new Event( LOG ) );
}
}
}
class InstanceKey {}
The important thing here is that a message can be logged from anywhere using
MessageLogger.getInstance().log( "Your Message Here" );
and anything can be notified of when a message has been logged using
MessageLogger.getInstance().addEventListener( MessageLogger.LOG, listenerFunction );
at any point the current message log can be obtained using
MessageLogger.getInstance().getLog();
Create a new Logging class and have that class have a static constructor. Add your logging method to this class. Make the static constructor save the logging field to a private variable. Now before you call the logging method just call your static constructor with the input field you'd like to use. This will create the class, set up the input field as the destination, and now you can simply just call the log function from anywhere.
Traditionally, the way you let static methods interact with private variables is to pass them in. Pass in a pointer to your textbox.
so instead of
public static function getTimeStamp():uint { ... }
you have
public static function writeTimeStamp(messageBox):uint { ... }
The syntax might be incorrect as I'm not an AS dev but, do you see what I mean? From there, that block of code can access messageBox as if it were a local variable. Well it is.
(I renamed the method name for clarity. You could even stop it returning a variable if it doesn't need to but you'll need to change the declaration further.)
In your updated post the debug text field needs to be added to the display list somewhere. Right now it looks like it is just created and used during that log method. You add display objects to the display list by calling addChild( displayObject:DisplayObject ):Boolean; on a DisplayObjectContainer that is already a child of stage.

Creating a custom trace() class in AS3

I got this idea of expanding my trace() messages.
Why
trace() is all over my code, I want to turn them on/off by a simple command and maybe add some sort of priority functionality to the trace(), i.e.
myTrace.TraceMsg("loosehere",debugme, 0);
myTrace.TraceMsg("winhere",debugme, 1);
And when I run, only the one with the higher priority, "1" in this case, shows.
There is a lot more functionality I would like to add as well, like logging messages to file and so on.
Problem
How do trace() work? -Is it possible to overload trace() somehow? -How would I implement the custom TraceMsg(what code here?) method?
Having some serious problems finding info on this subject on our favourite search engine, so any help would be appreciated.
I have come up with a rather efficient, yet tedious way of using my own trace() function in Flash only projects, but calling it simply with
trace("this", "that", "and that too");
I basically implement one trace() method in every class of my project, that calls a public function (so that i can call the real trace() function from there.
here is what I do : in every class I call this
include "trace_implementation.as";
in the .as file comes a simple method implementation (it could be a static method too).
public function trace(... arguments){
for(var i in arguments){
myTrace(arguments[i]);
}
}
and the myTrace function is defined in its own myTrace.as file
package pt.utils{
import flash.external.ExternalInterface
public function myTrace(_s:String):void{
trace(_s);// this will call the original flash trace() function
ExternalInterface.call("console.log", _s);// to get traces outside of flash IDE
/*implement what you want here*/
}
}
so now when I compile with "omit trace actions", my whole debugging is ignored as if I used trace() simply.
the really good part here is that you could implement custom actions depending on instructions you give in the trace, so :
trace(Debug.DEBUG_MESSAGE, "message to output in debug");
trace(Profile.START_PROFILING, this, 'name');
/*do heavy code*/
trace(Profile.STOP_PROFILING, this);
then dispatch it from myTrace, or a Tracer class or anything :)
Hope this helps future tracers.
trace() itself is a top-level function, not a class, so unfortunately we cannot extend it. That being said, we can utilize it in a simple class to do just what it does normally, only in this case the trace is based on conditions (i.e. Boolean - true|false, etc). First we create the Trace class, which we wouldn't instantiate ourselves because we are utilizing a Factory design pattern through the class below, Tracer. Tracer is built around the singleton design pattern, yet utilizes the Factory pattern to instantiate instances of Trace, when the trace method of Tracer is called.
//This class is handled by Tracer, which is right below it.
//You WILL NOT instantiate these, nor hold references.
package
{
public class Trace
{
private function _value:*;
private function _trace:Boolean;
public function Trace(pValue:*, pTrace:Boolean):void
{
_value = pValue;
_trace = pTrace;
}
public function get value():*
{
return _value;
}
public function get trace():Boolean
{
return _trace;
}
}
}
//This is the important class and the only one you will work with.
package
{
/**
*Utilizes Singleton and Factory design patterns.
*/
public class Tracer
{
private var _traceArray:Array;
private static var _instance:Tracer;
public function Tracer(pvt:PrivateClass = null):void
{
if(pvt == null)
{
throw(new Error("You cannot instantiate this class directly, please use the static getInstance method."));
}
_init();
}
public static function getInstance():Tracer
{
if(Tracer._instance == null)
{
Tracer._instance = new Tracer(new PrivateClass());
}
return Tracer._instance;
}
public function trace(pValue:*, pTrace:Boolean):void
{
var trace:Trace = new Trace(pValue, pTrace);
if(trace.pTrace)
{
trace(pValue);
}
}
//Since we have the option for individual traces to be disabled
//I provide this to get access to any and all later.
public function traceAll():void
{
traceStr:String = _traceArray.toString();
}
public function get traceables():Array
{
return _traceArray;
}
//Here we provide a method to trace all, even if set to false in their constructor.
private function _init():void
{
_traceArray = new Array();
}
}
}
//Here we create a class that is OUTSIDE of the package.
//It can only be accessed from within this class file. We use this
//to make sure this class isn't instantiated directly.
class PrivateClass
{
function PrivateClass():void
{
trace('can only be accessed from within this class file');
}
}
//Now for use in doc class
package
{
import flash.display.Sprite;
import flash.events.Event;
//No need to import Tracer and Trace, they are also in the
//unnamed package.
public class DocumentClass extends Sprite
{
private var _tracer:Tracer;
public function DocumentClass():void
{
if(stage) _init();
else addEventListener(Event.ADDED_TO_STAGE, _init);
}
private function _init(e:Event = null):void
{
_tracer = Tracer.getInstance();
_tracer.trace(10*20, false);
_tracer.trace(10*20, 0); //SAME AS ABOVE
_tracer.trace("I love AS3", true); //traces
_tracer.traceAll(); //Would trace: 200, 200, I love AS3
}
}
}
Keep in mind this is off the hip and very well could have a bug or two, but the idea is there; That is to say that this is not tested, it is merely to give you an idea of how you might implement this.
I hope this helps.
Look at the Flex logging API, particularly the section: Implementing a custom logger with the logging API.
Look up the TraceTarget class as well.
You can't override trace itself, but for ease of typing I like to create a global function called 'tr'. It's a little known fact that you can create global functions in AS3, but it's easy.
Create a file called tr.as inside you main source directory (not in a subdirectory or package), with the contents:
package {
public function tr(msg:String, ...):void {
// add custom trace logic here
trace("tr message: "+msg);
}
}
If you need to have a lot of logic or static storage variables etc, it might be better to make a separate static class, and have the global tr function call out to that, such as:
package {
import org.code.MyTracer;
public function tr(msg:String, ...):void {
MyTracer.tr(msg); // all the tracing logic is inside the MyTracer class
}
}
Here is a super simple custom trace function I use. debugFlag can be set to true/false
elsewhere in the package.
public static function myTrace(... vars) :void {
if (debugFlag) {
var output:Array = new Array;
for each (var arg in vars) {
output.push(arg);
}
trace(output);
}
}
In AS2, it was possible to override the global trace function by doing something like this (taken from memory, might be a bit wrong but the gist of it is there):
public static var realTrace:Function = _global["trace"];
// This is put in some init code somewhere
_global["trace"] = myTrace;
public static function myTrace(... args):void
{
// Do whatever you want with args here, build a nice formatted string or whatever
// before passing to realTrace. Using with MTASC one could add line numbers, class
// names and all sorts of nice meta data. Or just return should you want to turn
// tracing off.
realTrace.apply(args);
}
Unfortunately I haven't found a way to do the same in AS3. Yet.
Trace is a top-level function, so you can't override it, and as far as I know, it does not fire any events. Since it's a top-level function (not contained in any named package), you can use it without import statements.
Here is an example of a top-level "Tracer" class that you can use in place of trace without import statements.
Just call "Tracer.write" or "Tracer.writeError" for tracing Error objects.
"Tracer.write" accepts a variable number of arguments, just like the built-in trace function. "Tracer.writeError" is a helper method that allows you to easily trace Error objects.
Features:
Calls built-in trace.
Keeps a log of all your calls to Tracer.write as an array of strings.
The call log is accessible as a string through getText, which joins all elements in the array with a newline character and will optionally tack on line numbers!
Fires events when new lines are added to the log, so if you have some kind of display window for the log, the display window can listen for Tracer events to update the log display in real-time as the events occur. This is great for displaying trace events when running inside a web browser or stand-alone player.
-Tracer class definition
package
{
import flash.events.EventDispatcher;
public class Tracer extends EventDispatcher
{
private static var traced_text:Array = new Array( "--Start of Trace Log--" );
public static var enabled:Boolean = true;
private static var suspended:Boolean = false;
public static var instance:Tracer = new Tracer();
public static const newline:String = "\n"; //workaround for TextField.appendText bug.. use "\n" instead of "\r". See note and link to bug post in getText method
public function Tracer()
{
}
static public function write( ...args ):void
{
if (enabled && !suspended)
{
trace.apply( null, args );
var text:String = args.join( newline );
var next_index:int = traced_text.length;
traced_text.push( text );
suspended = true; //prevent recursive calls from TracerEvent handler
instance.dispatchEvent( new TracerEvent( text, next_index ) );
suspended = false;
}
}
static public function writeError( e:Error ):void
{
write( "errorID: " + e.errorID, "errorName: " + e.name, "errorMessage: " + e.message, "stackTrace: " + e.getStackTrace() );
}
static public function getText( include_line_numbers:Boolean ):String
{
var line_count:int = traced_text.length;
var lines:Array = traced_text; //store pointer to traced_text; pointer may be changed to reference an altered array that includes line numbers
if (include_line_numbers) //create temporary trace log copy with altered lines; allows quick call to join at end
{
var new_lines:Array = new Array();
for (var i:int = 0; i < line_count; i++)
new_lines.push( i.toString() + ": " + lines[i] );
lines = new_lines;
}
return lines.join( newline ); //do not include last newline character (workaround for bug in appendText method (https://bugs.adobe.com/jira/browse/FP-1982); I have to call appendText with newline character first, otherwise it has issues like not acknoledging the newline thats already there at the end).
}
static public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void
{
instance.addEventListener(type, listener, useCapture, priority, useWeakReference);
}
static public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void
{
instance.removeEventListener(type, listener, useCapture);
}
static public function willTrigger(type:String):Boolean
{
return instance.willTrigger(type);
}
static public function hasEventListener(type:String):Boolean
{
return instance.hasEventListener(type);
}
}
}
-TracerEvent class definition
package
{
import flash.events.Event;
public class TracerEvent extends Event
{
public static const WRITE:String = "te_write";
public var text:String;
public var index:int; //index of newly traced text in the traced_text array (trace log)
public function TracerEvent( text:String, index:int )
{
super( WRITE, false, false );
this.text = text;
this.index = index;
}
override public function clone():Event
{
return new TracerEvent( text, index );
}
}
}
As mentioned below, there is no way to override trace (at least not if you want your traces to reach the output stream), but it's actually very easy to create your own universally accessable logging function. Plus, you can even define a universally accessable boolean to turn logging on or off:
log.as (note that the filename must reflect the name of the function)
package {
function log(... arguments):void {
trace("Custom logging FTW!");
if (logEnabled)
trace(arguments);
}
}
logEnabled.as (note that the filename must reflect the name of the variable)
package {
var logEnabled:Boolean = true;
}
Main.as
package {
import flash.display.MovieClip;
public class Main extends MovieClip {
public function Main() {
log("Testing");
logEnabled = false;
log("Testing2");
}
}
}
Response
Custom logging FTW!
Testing
Custom logging FTW!
you dont need to override it , just create a function in your project and call it trace then any trace call will point to this.trace ;)
function trace(... arguments){
yourfunction(arguments);
}