External API example: Communicating between ActionScript and JavaScript in a web browser
This sample application demonstrates appropriate techniques for communicating between ActionScript and JavaScript in a web browser, in the context of an Instant Messaging application that allows a person to chat with him or herself (hence the name of the application: Introvert IM). Messages are sent between an HTML form in the web page and a SWF interface using the external API. The techniques demonstrated by this example include the following:
Properly initiating communication by verifying that the browser is ready to communicate before setting up communication
Checking whether the container supports the external API
Calling JavaScript functions from ActionScript, passing parameters, and receiving values in response
Making ActionScript methods available to be called by JavaScript, and performing those calls
To get the application files for this sample, see FlashPlatformAS3DevGuideExamples.zip. The Introvert IM application files can be found in the Samples/IntrovertIM_HTML folder. The application consists of the following files:
File | Description |
---|---|
IntrovertIMApp.fla or IntrovertIMApp.mxml | The main application file for Flash (FLA) or Flex (MXML). |
com/example/programmingas3/introvertIM/IMManager.as | The class that establishes and manages communication between ActionScript and the container. |
com/example/programmingas3/introvertIM/IMMessageEvent.as | Custom event type, dispatched by the IMManager class when a message is received from the container. |
com/example/programmingas3/introvertIM/IMStatus.as | Enumeration whose values represent the different "availability" status values that can be selected in the application. |
html-flash/IntrovertIMApp.html or html-template/index.template.html | The HTML page for the application for Flash (html-flash/IntrovertIMApp.html) or the template that is used to create the container HTML page for the application for Adobe Flex (html-template/index.template.html). This file contains all the JavaScript functions that make up the container part of the application. |
Preparing for ActionScript-browser communication
One of the most common uses for the external API is to allow ActionScript applications to communicate with a web browser. Using the external API, ActionScript methods can call code written in JavaScript and vice versa. Because of the complexity of browsers and how they render pages internally, there is no way to guarantee that a SWF document will register its callbacks before the first JavaScript on the HTML page runs. For that reason, before calling functions in the SWF document from JavaScript, your SWF document should always call the HTML page to notify it that the SWF document is ready to accept connections.
For example, through a series of steps performed by the IMManager class, the Introvert IM determines whether the browser is ready for communication and prepares the SWF file for communication. The first step, determining when the browser is ready for communication, happens in the IMManager constructor, as follows:
public function IMManager(initialStatus:IMStatus)
{
_status = initialStatus;
// Check if the container is able to use the external API.
if (ExternalInterface.available)
{
try
{
// This calls the isContainerReady() method, which in turn calls
// the container to see if Flash Player has loaded and the container
// is ready to receive calls from the SWF.
var containerReady:Boolean = isContainerReady();
if (containerReady)
{
// If the container is ready, register the SWF's functions.
setupCallbacks();
}
else
{
// If the container is not ready, set up a Timer to call the
// container at 100ms intervals. Once the container responds that
// it's ready, the timer will be stopped.
var readyTimer:Timer = new Timer(100);
readyTimer.addEventListener(TimerEvent.TIMER, timerHandler);
readyTimer.start();
}
}
...
}
else
{
trace("External interface is not available for this container.");
}
}
First of all, the code checks whether the external API is even available in the
current container using the ExternalInterface.available
property. If so, it
begins the process of setting up communication. Because security exceptions and
other errors can occur when you attempt communication with an external
application, the code is wrapped in a try
block (the corresponding catch
blocks were omitted from the listing for brevity).
The code next calls the isContainerReady()
method, listed here:
private function isContainerReady():Boolean
{
var result:Boolean = ExternalInterface.call("isReady");
return result;
}
The isContainerReady()
method in turn uses ExternalInterface.call()
method
to call the JavaScript function isReady()
, as follows:
<script language="JavaScript">
<!--
// ------- Private vars -------
var jsReady = false;
...
// ------- functions called by ActionScript -------
// called to check if the page has initialized and JavaScript is available
function isReady()
{
return jsReady;
}
...
// called by the onload event of the <body> tag
function pageInit()
{
// Record that JavaScript is ready to go.
jsReady = true;
}
...
//-->
</script>
The isReady()
function simply returns the value of the jsReady
variable.
That variable is initially false
; once the onload
event of the web page has
been triggered, the variable's value is changed to true
. In other words, if
ActionScript calls the isReady()
function before the page is loaded,
JavaScript returns false
to ExternalInterface.call("isReady")
, and
consequently the ActionScript isContainerReady()
method returns false
. Once
the page has loaded, the JavaScript isReady()
function returns true
, so the
ActionScript isContainerReady()
method also returns true
.
Back in the IMManager constructor, one of two things happens depending on the
readiness of the container. If isContainerReady()
returns true
, the code
simply calls the setupCallbacks()
method, which completes the process of
setting up communication with JavaScript. On the other hand, if
isContainerReady()
returns false
, the process is essentially put on hold. A
Timer object is created and is told to call the timerHandler()
method every
100 milliseconds, as follows:
private function timerHandler(event:TimerEvent):void
{
// Check if the container is now ready.
var isReady:Boolean = isContainerReady();
if (isReady)
{
// If the container has become ready, we don't need to check anymore,
// so stop the timer.
Timer(event.target).stop();
// Set up the ActionScript methods that will be available to be
// called by the container.
setupCallbacks();
}
}
Each time the timerHandler()
method gets called, it once again checks the
result of the isContainerReady()
method. Once the container is initialized,
that method returns true.
The code then stops the Timer and calls the
setupCallbacks()
method to finish the process of setting up communication with
the browser.
Exposing ActionScript methods to JavaScript
As the previous example showed, once the code determines that the browser is
ready, the setupCallbacks()
method is called. This method prepares
ActionScript to receive calls from JavaScript, as shown here:
private function setupCallbacks():void
{
// Register the SWF client functions with the container
ExternalInterface.addCallback("newMessage", newMessage);
ExternalInterface.addCallback("getStatus", getStatus);
// Notify the container that the SWF is ready to be called.
ExternalInterface.call("setSWFIsReady");
}
The setCallBacks()
method finishes the task of preparing for communication
with the container by calling ExternalInterface.addCallback()
to register the
two methods that will be available to be called from JavaScript. In this code,
the first parameter—the name by which the method is known to JavaScript (
"newMessage"
and "getStatus"
)—is the same as the method's name in
ActionScript. (In this case, there was no benefit to using different names, so
the same name was reused for simplicity.) Finally, the
ExternalInterface.call()
method is used to call the JavaScript function
setSWFIsReady()
, which notifies the container that the ActionScript functions
have been registered.
Communication from ActionScript to the browser
The Introvert IM application demonstrates a range of examples of calling
JavaScript functions in the container page. In the simplest case (an example
from the setupCallbacks()
method), the JavaScript function setSWFIsReady()
is called without passing any parameters or receiving a value in return:
ExternalInterface.call("setSWFIsReady");
In another example from the isContainerReady()
method, ActionScript calls the
isReady()
function and receives a Boolean value in response:
var result:Boolean = ExternalInterface.call("isReady");
You can also pass parameters to JavaScript functions using the external API. For
instance, consider the IMManager class's sendMessage()
method, which is called
when the user is sending a new message to his or her "conversation partner":
public function sendMessage(message:String):void
{
ExternalInterface.call("newMessage", message);
}
Once again, ExternalInterface.call()
is used to call the designated JavaScript
function, notifying the browser of the new message. In addition, the message
itself is passed as an additional parameter to ExternalInterface.call()
, and
consequently it is passed as a parameter to the JavaScript function
newMessage()
.
Calling ActionScript code from JavaScript
Communication is supposed to be a two-way street, and the Introvert IM
application is no exception. Not only does the Flash Player IM client call
JavaScript to send messages, but the HTML form calls JavaScript code to send
messages to and ask for information from the SWF file as well. For example, when
the SWF file notifies the container that it has finished establishing contact
and it's ready to communicate, the first thing the browser does is call the
IMManager class's getStatus()
method to retrieve the initial user availability
status from the SWF IM client. This is done in the web page, in the
updateStatus()
function, as follows:
<script language="JavaScript">
...
function updateStatus()
{
if (swfReady)
{
var currentStatus = getSWF("IntrovertIMApp").getStatus();
document.forms["imForm"].status.value = currentStatus;
}
}
...
</script>
The code checks the value of the swfReady
variable, which tracks whether the
SWF file has notified the browser that it has registered its methods with the
ExternalInterface class. If the SWF file is ready to receive communication, the
next line ( var currentStatus =
...) actually calls the getStatus()
method in
the IMManager class. Three things happen in this line of code:
The
getSWF()
JavaScript function is called, returning a reference to the JavaScript object representing the SWF file. The parameter passed togetSWF()
determines which browser object is returned in case there is more than one SWF file in an HTML page. The value passed to that parameter must match theid
attribute of theobject
tag andname
attribute of theembed
tag used to include the SWF file.Using the reference to the SWF file, the
getStatus()
method is called as though it's a method of the SWF object. In this case the function name "getStatus
" is used because that's the name under which the ActionScript function is registered usingExternalInterface.addCallback()
.The
getStatus()
ActionScript method returns a value, and that value is assigned to thecurrentStatus
variable, which is then assigned as the content (thevalue
property) of thestatus
text field.
Note: If you're following along in the code, you've probably noticed that in the
source code for the updateStatus()
function, the line of code that calls the
getSWF()
function, is actually written as follows: var currentStatus =
getSWF("\${application}").getStatus(); The ${application}
text is a
placeholder in the HTML page template; when Adobe Flash Builder generates the
actual HTML page for the application, this placeholder text is replaced by the
same text that is used as the object
tag's id
attribute and the embed
tag's name
attribute ( IntrovertIMApp
in the example). That is the value
that is expected by the getSWF()
function.
The sendMessage()
JavaScript function demonstrates passing a parameter to an
ActionScript function. ( sendMessage()
is thefunction that is called when the
user presses the Send button on the HTML page.)
<script language="JavaScript">
...
function sendMessage(message)
{
if (swfReady)
{
...
getSWF("IntrovertIMApp").newMessage(message);
}
}
...
</script>
The newMessage()
ActionScript method expects one parameter, so the JavaScript
message
variable gets passed to ActionScript by using it as a parameter in the
newMessage()
method call in the JavaScript code.
Detecting the browser type
Because of differences in how browsers access content, it's important to always
use JavaScript to detect which browser the user is running and to access the
movie according to the browser-specific syntax, using the window or document
object, as shown in the getSWF()
JavaScript function in this example:
<script language="JavaScript">
...
function getSWF(movieName)
{
if (navigator.appName.indexOf("Microsoft") != -1)
{
return window[movieName];
}
else
{
return document[movieName];
}
}
...
</script>
If your script does not detect the user's browser type, the user might see unexpected behavior when playing SWF files in an HTML container.