Flash has been communicating with JavaScript for a long time through getURL and fscommand, but with Flash 8 it's easier than ever. With the ExternalInterface class, you cannot only call JavaScript functions, but also have JavaScript call Flash functions. And now that JavaScript is getting more and more publicity in the form of AJAX (Asynchronous JavaScript and XML), the ability to seamlessly integrate your Flash content within your HTML content is essential.
This article is going to cover some basic uses of ExternalInterface and move into some of the more undocumented ways to use it. Then we will finish up by building a small image injector that will take a selected image from one browser window and push it into another HTML page without the use of a LocalConnection object.
ExternalInterface Basics
The method being used to make calls to JavaScript is the static call method of the ExternalInterface class. Here is the basic syntax for calling this method:
ExternalInterface.call(function:string, parameters:object);
The first parameter is the name of the function you're calling as a string. And following that parameter are all the parameters (separated by commas) for the function being called. The parameters being passed to the JavaScript function can be any valid ActionScript data type.
Here is an example of calling a function in JavaScript:
//first, import the necessary class
import flash.external.ExternalInterface;
//call the helloWord function
ExternalInterface.call("helloWorld", getTimer());
Not only are we calling the helloWorld function in JavaScript (which we will write next), but we are passing it the number of milliseconds (see Image 1) that have passed before the function is called using getTimer(). The next step is to publish the SWF and the HTML file, then open the HTML file and place this code after the closing </head> tag.
<script>
function helloWorld(ms){
alert("It took "+ms+" milliseconds to say hello world");
}
</script>
What this function will do is take the amount of milliseconds being passed to it and use it in a statement that will appear in an alert box.
Now you can save the HTML file and publish it along with the SWF file up to a web server to test it, where you should see an alert box similar to the following image (see Image 2).
The reason the files must be on a web server instead of being able to test it locally is because of the Flash 8 security sandbox. The allowScriptAccess parameter (found in both the object and the embed tag) in the HTML has a default value of "sameDomain", which means that the file will not allow calls to JavaScript from Flash while running locally on your machine (or from Flash files running from different domains). You can of course fix that by changing the value of allowScriptAccess to "always" in both places.
Returning Values
With ExternalInterface, you can also get a return value if the JavaScript function you're calling has one. To get the return value, just set a variable directly to the ExternalInterface.call() you are making like this:
var squareNum = ExternalInterface.call("square", 4);
Providing you have a JavaScript function that will do the work, the variable squareNum will now have a value of 16.
A more practical example might be when a user has forgotten his or her password. Most systems will send an email with either the password or a link to a page where the password can be reset, but some systems require you to be able to answer a simple question that was set by the user such as "What is the name of your pet?" or "What is your mother's maiden name?". These questions are usually presented on a separate page, but they could be easily presented right from the current page in a prompt box like the following example.
As you can see from the following image, the setup for the stage is your general User Name/Password style setup, but the "Forgot Password" Button component has an instance name of forgot_butn. And then the ActionScript in the first frame will look like this:
//import the class we need
import flash.external.ExternalInterface;
//the question and answer
var question:String = "Who is your favorite cartoon character?";
var answer:String = "Bender";
forgot_butn.clickHandler = function(){
//call the prompt and wait for the answer
var returnedAnswer = ExternalInterface.call("prompt", question);
//make sure the user didn't hit cancel
if(returnedAnswer){
if(returnedAnswer == answer){
var msg:String = "That is correct! Your password has been emailed to you.";
}else{
var msg:String = "That is incorrect!";
}
//send the results to an alert box
ExternalInterface.call("alert", msg);
}
}
The code above should look somewhat familiar. It initially imports the class we need to work with JavaScript. Then both the question and the answer variables are set. Next is the event for when a user clicks the "Forgot Password" button, which directly calls the prompt function without any need for a custom JavaScript function. Once a user has filled in the answer to the prompt question, the result is returned to returnedAnswer for comparison against the real answer. As long as the user does not press the cancel button in the prompt, they will receive an alert telling them whether or not the answer is correct.
You can publish this file and the HTML up the server, and then click "Forgot Password" to see a prompt similar to the following image (see Image 3). And of course this technique becomes even more powerful when hooking it into a backend database to make it dynamic.
As mentioned earlier, notice how the code in this example has no need for custom functions in JavaScript. Instead, you can directly call all the available JavaScript functions directly from Flash, but not just functions.
Setting Properties
Although it is undocumented, you can in fact set properties in JavaScript right from Flash using ExternalInterface. The key to setting properties from the call method is using an equal sign (=) after the property name in the first parameter, and then putting the new value of that property as the second parameter. Here is an example that will set the background color of the HTML page to red:
ExternalInterface.call("document.bgColor=", "#ff0000");
As a more practical example, you can also set properties of individual elements using first the getElementById method of the document object and then set the content of that element using the innerHTML property.
The first thing you will need in this new file is a TextArea component and a Button component. Give the TextArea an instance name of text_ta and the Button an instance name of submit_butn with the label property of "Submit" like the figure below. Then the ActionScript to make it work looks like this:
//import the class we need
import flash.external.ExternalInterface;
//when the button is clicked, send the text
submit_butn.clickHandler = function(){
ExternalInterface.call("document.getElementById
(Œcontent').innerHTML=", text_ta.text);
}
Notice we are looking for an element with the id attribute of "content". We have to put that into the HTML next, so publish both the SWF and the HTML. Then open the HTML doc and add this line after the closing </object> tag for the SWF:
<div id="content"></div>
This is where the content is going to appear. Save the file and push both the HTML and the SWF to a web server (or change the allowScriptAccess properties to "always" for local testing) and test it. You will see that when you click the submit button, the text in the TextArea component will appear under the SWF. And because your sending text that will render as HTML, you can use HTML tags like the following figure shows (see Image 4).
You can also change properties and call functions in another HTML window, as you will see in the final example.
Building the Image Injector
Now let's take what has been covered so far and apply it to a real world example by building an image injector. The idea for this example is that there can be a pop up window that will control an image (see Image 5) in the opening window. This is useful for testing different looks of a page, or in conjunction with a content management system (which is why the injector was originally created).
The first part of the injector example will be the page that will be receiving the new images. Open up Dreamweaver, start a new HTML page and place this code in it:
<html>
<head>
<title>My Favorite Picture</title>
</head>
<script>
function openInjector() {
window.open(ŒswapPic.html','changer','toolbars=no,resizeable=no,
scrollbars=no,width=410,height=410');
}
</script>
<body>
<h3>This is my Favorite image:</h3>
<a href="javascript: openInjector();"><div id="imageLoader">
<img src='imgs/water.jpg' border="0" /></div></a>
"p>(click the image to change it)</p>
</body>
</html>
The above HTML does a few things that are important. First it creates the JavaScript function for when someone clicks on the image (see Image 6). That function will open the pop up, which we will create later ("swapPic.html"). We also create the <div> tag we need with a default image already in it. And the entire div is wrapped with an <a> tag that will make it so when the user clicks the image, the JavaScript function will be called, and the pop up will appear. Before the actual injector is built, there are a couple of PHP pageshat will make the injector easier to manage. The first is a page that will grab all the JPEG images from a given directory and return them to Flash. The second page will create thumbnails of those images for displaying in Flash. Because the full size images can be of different size, it's important to make the thumbnails consistent for being displayed in the injector.
fileList.php
<?php
$fileList;
$picDir = $_POST[ŒsentDir'];//get the directory
$dir = opendir($picDir);//open the directory
while($file = readdir($dir)){
//we only need the JPEGS for this example
if(substr_count($file, ".jpg") > 0){
$fileList .= $file . "~";
}
}
//close the directory
closedir($dir);
//send the files back to Flash
echo "files=" . $fileList
?>
This file opens the directory passed to it from Flash, and returns a string of all the JPEG images separated by a tilde (~) character.
makeThumb.php
<?php
// The file, thumbnail size and zoom level
$filename = $_GET[ŒsentFile'];
$thumbSize = $_GET[ŒsentSize'];
$imgZoom = $_GET[ŒsentZoom'];
// Content type being returned
header(ŒContent-type: image/jpeg');
// Get new dimensions
list($width, $height) = getimagesize($filename);
if($width > $height){
$new_width = $height * $imgZoom;
}else{
$new_width = $width * $imgZoom;
}
//Set the starting point for the thumbnail creation
$sX = ($width-$new_width)/2;
$sY = ($height-$new_width)/2;
// create the image from the original
$image_p = imagecreatetruecolor($thumbSize, $thumbSize);
$image = imagecreatefromjpeg($filename);
imagecopyresampled($image_p, $image, 0, 0, $sX, $sY, $thumbSize,
$thumbSize, $new_width, $new_width);
// output JPEG
imagejpeg($image_p, null, 100);
?>
When this file is called from Flash, you pass it three parameters:
Now the HTML and the PHP pages are done, the final piece is the injector itself. Before anything is created on the stage, save this file as "swapPic.fla". Then create a second layer and call the top one ActionScript and the bottom one content. Reset the stage to 400x400 to fit nicely in the pop up. There is only a small header and a horizontal bar in the content layer because most of the interface will be built with ActionScript.
In the ActionScript layer, place this code:
import flash.external.ExternalInterface;
import flash.filters.DropShadowFilter;
//a filter to give the thumbnails a little depth
var myDrop:DropShadowFilter = new
DropShadowFilter(4,45,0x000000,100,4,4,1,3);
//the directory to get images from
var dir:String = "imgs";
//main path to files
var mPath:String = "http://www.yourServer.com/";
//the function called when a thumbnail is selected
function sendFile(fl){
ExternalInterface.call
("opener.document.getElementById(ŒimageLoader').innerHTML=",
"<img src='"+dir+"/"+fl+"' border='0' />");
}
//the LoadVars object for collecting the image names from fileList.php
var imgs_lv:LoadVars = new LoadVars();
imgs_lv.onLoad = function(success){
if(success){
//the initial setting for columns and rows
var c = r = 0;
var imgs_array = this.files.split("~");
var tLength = imgs_array.length - 1;
var i = -1;
while(++i < tLength){
var img:MovieClip = createEmptyMovieClip("img"+i+"_mc", i+1);
img.createEmptyMovieClip("target_mc",
1).loadMovie(mPath+"makeThumb.php?sentFile="+dir+"/"+imgs_array[i]+"&sentSiz
e=40&sentZoom=1");
img._x = (c*50)+20;
img._y = (r*50)+55;//the plus 55 is to fit under the title at the top
c++;
if(c == 7){
c = 0;
r++;
}
//when a thumbnail is selected, call the function
img.file = imgs_array[i];
img.onRelease = function(){
sendFile(this.file);
}
//set the dropshadow
img.filters = [myDrop];
}
}else{
ExternalInterface.call("alert", "An error occured with the images.");
}
}
//go get the images
imgs_lv.sentDir = dir;
imgs_lv.sendAndLoad(mPath+"fileList.php", imgs_lv, "POST");
Most of this code is for controlling the alignment of the thumbnails. The first part grabs the two classes we need, including the Flash 8 DropShadow class to add some depth to the thumbnails. After that, we create a variable to hold the directory we are going to pass to the PHP script as well as a variable to hold the main path to the PHP scripts. The next part is the function that will be called when a thumbnail is clicked. Notice that in the call method, we are not only passing the property we want to access (the innerHTML property of the ŒimageLoader' element), but we are also using the keyword opener before we access it. What this keyword does is tell JavaScript we want to access the element in the HTML page that opened this HTML page. This means that not only can we access all the properties of the current HTML page we're in, but other HTML pages as well (provided they are on the same domain when setting allowScriptAccess to "sameDomain").
After that, a LoadVars object is created to handle the communication between the PHP and Flash. Within its onLoad event, a while loop is used to cycle through all the images and create thumbnails. This is where the makeThumb.php script comes in handy, because we know each image being returned will consistently be the same size. While we are creating the thumbnails, we set the onRelease event for them so when they are clicked, the sendFile function will be called updating the image in the other HTML page. Finally, we set the sentDir property of the LoadVars object and fetch all the images in that directory.
The only thing left to do is to publish and test it. So after you load the SWF, HTML and PHP pages (or pages that do the same thing as the PHP ones) to your server, you should be able to load the initial page, click on the image, which will open the Image Injector pop up, and swap out images in real time to see which one fits the best.
What's next? As much as I hate to use a cliché', you are bound only by your imagination. From some of the techniques in this article, you can see that not only do you have total access to JavaScript from Flash, but you can call functions, methods and even set properties of different pages from a single pop up window. Imagine being able to control layout and colors of one window from another, it will save a lot time on trial and error designing.