Can I call a function in a swf from JavaScript?

This question comes in from a client who wanted to know if he could run a function in a swf movie when the browser was closed. This week I'm actually working on JavaScript Flex integration for a project, so I can kill two birds with one stone. This is his actual [edited] question:


Is there a way to call specific functionality when a Flex application is being unloaded (on exit)?

The application in question was integrating with Omniture's Flash tracking suite to track video playback. The Omniture server was being pinged for tracking purposes when the user performed various actions. When the browser was shut down they wanted to trigger the "Stopped watching video" event so they would know how much of the video the user watches.

I know this use case rather well, because DotComIt has done a lot of Omniture Flex integration for this client.

The questions, can we do this? And how?

JavaScript can run a function when the browser closes using the "onunload" event of the HTML body tag. If you Google it you can find plenty of examples.

So, from JavaScript how do we execute a Flex function? Adobe has some great documentation on this topic. Unfortunately, I could not get the Adobe sample to work in Firefox 2 or IE6. I kept getting JavaScript errors. Google once again was my friend and I came across Paranoid Ferret's tutorial on the subject. That blog post had the magic easy button to make it all work.

So without further adieu, here is the modified Adobe doc example:


<?xml version="1.0"?>
<!-- wrapper/AddCallbackExample.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="initApp()">

<mx:Script><![CDATA[
import flash.external.*;

public function initApp():void {
ExternalInterface.addCallback("myFlexFunction",myFunc);
}

public function myFunc(s:String):void {
l1.text = s;
}
]]>
</mx:Script>

<mx:Label id="l1"/>

</mx:Application>

This Flex code is not much different than the Adobe doc example. In the Script I added the "" ( although the compiler was compiling w/o those in there). When you type a in Flex builder thoe cdata is automatically added in. I guess I don't know why, though.

Anyway, the point is that there were no problems with the Flex side of things. The problem with the JavaScript side:


<html><head>
<title>wrapper/AddCallbackWrapper.html</title>
</head>
<body scroll='no'>

<SCRIPT LANGUAGE="JavaScript">
function callApp() {
window.document.title = document.getElementById("newTitle").value;
mySwf.myFlexFunction(window.document.title);
}
</SCRIPT>

<h1>AddCallback Wrapper</h1>

<form id="f1">
Enter a new title: <input type="text" size="30" id="newTitle" onchange="callApp()">
</form>

<table width='100%' height='100%' cellspacing='0' cellpadding='0'>
<tr><td valign='top'>
<object id='mySwf' classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000' codebase='http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0' height='200' width='400'>
<param name='src' value='JavaScriptFlex.swf'/>
<param name='flashVars' value=''/>
<embed name='mySwf' src='JavaScriptFlex.swf' pluginspage='http://www.macromedia.com/shockwave/download/index.cgi?P1_Prod_Version=ShockwaveFlash' height='100%' width='100%' flashVars=''/>
</object>
</td></tr>
</table>

</body></html>

The code to drop the swf into the page is fine.

The text input and the onchange is fine. However, when the onChange method is called I get this error in Firebug:

mySwf.myFlexFunction is not a function

Rather frustrating. JavaScript cannot find the Flex Function. Paranoid Ferret had the answer with this JavaScript function:


// This function returns the appropriate reference,
// depending on the browser.
function getFlexApp(appName) {
if (navigator.appName.indexOf ("Microsoft") !=-1) {
return window[appName];
} else {
return document[appName];
}
}

This function is used to get a reference to the swf. And we'll change the callApp function too:


function callApp() {
window.document.title = document.getElementById("newTitle").value;
getFlexApp('mySwf').myFlexFunction(window.document.title);
}

And bammo, everything starts working in both IE6 and FireFox 2.

Now, can you call the function in the unload event when the browser closes? Yes you can! The new body tag:


<body scroll='no' onunload="callApp2()">

And the new JavaScript block:


<SCRIPT LANGUAGE="JavaScript">
// This function returns the appropriate reference,
// depending on the browser.
function getFlexApp(appName) {
if (navigator.appName.indexOf ("Microsoft") !=-1) {
return window[appName];
} else {
return document[appName];
}
}

function callApp() {
window.document.title = document.getElementById("newTitle").value;
getFlexApp('mySwf').myFlexFunction(window.document.title);
}

function callApp2(){
getFlexApp('mySwf').myFlexFunction('closing');
alert('closing');
}
</SCRIPT>

In the callApp2 function, I added an alert. When you close the browser the alert window pops up. This pauses the browser close so we can actually see that the swf has changed (for a split second).

Could there be timing issues with the swf making a call while the browser is closing? Possibly, it would need more testing with a remote call of some sort. But, the possibilities of this are intriguing for the use case in question (AKA tracking when a user stops watching a video).

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
Shimju David's Gravatar Thanks for sharing this. Really useful.
# Posted By Shimju David | 3/14/08 5:19 AM
Casey Bentz's Gravatar I would like to be able to open a browser popup and have it send back information to a function in my swf, which is in a different browser. First, does this make sense? Second, is it possible?
# Posted By Casey Bentz | 10/19/10 11:18 AM
Jeffry Houser's Gravatar Casey,

It depends on what is in your browser popups.

I do not believe an HTML browser popup can send information back to a SWF in another browser window.

If it 2 SWFs they should be able to communicate via LocalConnection, although I would expect that they both have to be served from the same domain for that to work. I haven't tested that, though.
# Posted By Jeffry Houser | 10/19/10 9:15 PM
Casey Bentz's Gravatar I actually got this figured out. First, I added ExternalInterface.addCallback("updateLatLon", displayLatLon); to my flex application. I then added a callback function to the to the html wrapper that flex builder creates when I create a new project.
var flexParams = "";
function callBack(latlon){
   flexParams = latlon;
   getFlashApp("index").updateLatLon(latlon);
}
function getFlashApp(appName) {
if (navigator.appName.indexOf("Microsoft") != -1) {
return window[appName];
} else {
return document[appName];
}
}
I then created a function in my html browser pop up to send the data to the html wrapper.
function sendLatLon(position)
{
window.opener.callBack(position);
}

So, I pass data from my Flex app to the html popup. I then do something in the popup which calls the callback function in the html wrapper that calls the function in the Flex app.
# Posted By Casey Bentz | 10/20/10 8:01 PM
All Content Copyright 2005, 2006, 2007, 2008, 2009 Jeffry Houser. May not be reused without permission
BlogCFC was created by Raymond Camden. This blog is running version 5.9.2.002.