views:

2379

answers:

2

Hi, I have a Javascript API, which should be usable with GWT and Flex. Using the FABridge it is really easy to call Javascript methods from AS3 and vice versa. But when I try to register a callback to an AS3 method in my Javascript API I get stuck. Here is a short code sample:

public function initApp():void {
    if (ExternalInterface.available) { 
     ExternalInterface.addCallback("foobar", foobar);
}
}

public function foobar():void {
    //the callback function
    Alert.show("Callback from API works!");
}

private function btnCallbackClicked():void {
    ExternalInterface.call("testAPICallbackFromJS", Application.application.foobar);
}

And the simple JS method:

function testAPICallbackFromGWT(callback){
  $clinit_26(); //added by the GWT compiler
  alert('callback to be launched 3 2 1');
  callback();
}

But this version does not work, because I always receive an empty function in my JS code. It seems that the FABridge is cutting the rest. Then I tried a different approach. I wrote a little JS method, which takes the name of the function and creates the callback from the JS side.

registerFlexCallback = function(registerMethod, callback, id) {
    /*
    workaround to create a callback for an AS method, which can be called by Javascript
     * registerMethod - Javascript method which shall be called for registration with the created callback as parameter
     * callback - AS method that shall be called by Javascript (available over the FABridge interface)
     * id - ID of the flash object (use Application.application.id in AS)
    */
    var swf = document.getElementById(id);
    eval(registerMethod + "(swf." + callback + ");");
};

This one works well with the Internet Explorer, but with no other browser. For example in Firefox I get the following error message:

NPMethod called on non-NPObject wrapped JSObject!

Can somebody tell me, what this error is about (maybe some kind of security issue)? Or does anyone have a better idea how to create callbacks for my AS3 methods which can be called by JS?

A: 

I notice two things right away

firstly it appears your ExternalInterface will die if the ExternalInterface is not ready.

public function initApp():void {
   if (ExternalInterface.available) { 
    ExternalInterface.addCallback("foobar", foobar);
}
}

I would add a timout and then try again so that it tries again until Externalinterface is ready.

Also I don't see the function "foobar" in your javascript code. I see callback passed in as a variable but without varifying that it is in fact 'foobar' this is hte kind of thing that can make testing a misserable event.

function testAPICallbackFromGWT(callback){
  $clinit_26(); //added by the GWT compiler
  alert('callback to be launched 3 2 1');
  callback();
}

I would simplify your testing example so that there are less moving parts.

// e.g. run just flash to javascript only
ExternalInterface.call("alert", "hello out there");

if that works

// establish the call from flash
ExternalInterface.addCallback("hello_out_there", foobar);

// and in javascript
alert(typeof('hello_out_there')); // will be 'function' if exists or undefined if ExternalInterface did not work

This way you can get a handle bit for bit what is working and where it breaks down.

Pay atention to the timing, if you can tigger your flash from button actions and your javascript from links you can illiminate a number of loading issues as well. of course you'll need to solve an autoload version for your launch but for testing manually triggered events can simplify things significantly.

also because it's javascript the browser is relevant.

I've seen consistent results in Firefox and Internet explorer that break down in safari and sometimes IE is the odd browser out.

Sometimes Firefox is the only one that breaks.

you just have to test them all.

Fire Crow
Thanks for the hint with the timeout, but I had no problems with calling JS methods from AS or calling AS methods from JS (with hard coded function names). That worked fine. My problem was that I couldn't create callbacks for my AS methods and use them within my JS methods.
SilentGert
+1  A: 

This is because functions don't serialize across the FABridge. Meaning in your

ExternalInterface.call("testAPICallbackFromJS", Application.application.foobar);

the second parameter will always be null. What I do is add a wrapper method on the HTML page via eval that points at my embed and therefore the added callback. So you have to add an extra, while annoying step:

ExternalInterface.addCallback("foobar", foobar);

var callBack:String = "";
var functionName:String = UIDUtil.createUUID; 
callBack = "function " + functionName + "( ){ " + 
"document.getElementById('applicationName').foobar(arguments);"+
"}";
ExternalInterface.call("eval", callback);


ExternalInterface.call("testAPICallbackFromJS", functionName);

The NPObject error you're seeing I'm pretty sure is a security error ( based on where it comes from in the FF code ) probably preventing you from dynamically injecting methods that can be eval'ed without the JS interpreter getting in the way.

I haven't even tried to compile the above so, hopefully you get the gist.

dan
Thank you very much for your help, it works now! Just two things, if you use UIDUtil.createUID() for creating the JS function name you should add a letter at the beginning and replace all "-", otherwise it doesn't seem to be a correct JS function name and won't work. Additionally I still have to eval() the functionName in my JS code, because it's just a String and no function pointer. Is there a better way to do this?
SilentGert
Good point, regarding the method names in JS. I actually don't use UUID, in my real code but, it's all I could think of! You can call them function directly by name in JS like this:this[functionName].apply(arrayOfParameters);but it's not much different then using eval.
dan