Diagram of languages spoken by each document

Captivate 2.0 doesn’t include the ability directly manipulate Actionscript. This has been problematic for people like myself who have Flash-based ‘players’ that load and unload both Captivate SWFs and Flash SWFs… we often need the Captivate SWF to perform some kind of action when it reaches its end.

In my case, I usually need the loaded Captivate SWF to tell the Flash container — an e-learning course interface — that a simulation has been successfully completed.

The figure on the right illustrates the problem: Captivate only allows developers to make Javascript calls, even though Captivate SWFs are running Actionscript under-the-hood!

Flash SWFs, on the other hand, traditionally use Actionscript. Granted, there’s a long history of Flash-Javascript tricks, but most of them have been very ‘hacky’ and not very robust.

The HTML container doesn’t recognize Actionscript at all; it only speaks Javascript.

The old solution: embedded SWFs

Diagram of languages spoken by each document (includes embedded SWF)

For a long time, the only reliable workaround had been to import a Flash SWF containing Actionscript into Captivate, and carefully place it on a Captivate slide. It works like this:

  1. Create a blank Flash SWF, then type a small amount of Actionscript into a frame script (such as calling function “doSomething()” on frame 1).
  2. In order for the imported SWF’s Actionscript to properly communicate with the Flash container, it must be prefixed with “_root.”; this corrects a scope issue and forces the Captivate movie to send the Actionscript call to its parent. The full line of Actionscript in the blank Flash SWF looks like this:
    _root.doSomething();
  3. The container SWF must contain the corresponding function. Something like this:
     function doSomething() {    //do something } 
  4. When the Captivate SWF’s play head reaches the frame containing the embedded SWF, any Actionscript contained in that embedded SWF is executed.

Click here to see a functioning sample. Download a ZIP containing the source files.

This is a simple solution, but it suffers from scope creep and is difficult to maintain. Who really wants to import a SWF every time they need to make an Actionscript call? I sure don’t. And if you ever want to change your Actionscript code, you need to re-export the SWF from Flash, and update the imported SWF in Captivate. Yuck.

Isn’t there a better way?

What about Javascript?

I got to thinking about it, and wondered (again) “Isn’t there a better way to do this?” Then I remembered two things: That Captivate supports Javascript calls, and that SWFObject — arguably the best way to embed Flash SWFs onto a web page — supports passing Javascript variables to a Flash SWF. What if I could make the Captivate SWF communicate with the Flash container SWF via Javascript and SWFObject?

Sad to say, no dice! SWFObject only passes variables when the Flash container SWF loads. I need to pass variables at various times, not just at ‘onload.’

Even though SWFObject was quickly shot down as a communication method, it got me thinking about Javascript as an alternative. Versions of Flash prior to Flash 8 could handle small snippets of Javascript via GetURL, but the GetURL hack is clumsy at best. I knew I didn’t want to go that route. But hang on a minute… what about Flash 8’s ExternalInterface class? It was designed to make Javascript-to-Actionscript communication a breeze! Having never really used it — it’s only supported in Flash Player 8+ and we’re still stuck supporting Flash Player 7 at the office — I decided to give it a whirl. I quickly created this example using Adobe’s tutorial [link no longer available]. First thoughts: Very intriguing and easy to get up and running, but can this be applied to Captivate SWFs loaded into Flash?

The short answer is… YES!

The new solution: Flash’s ExternalInterface class (Actionscript 2.0)

First I laid out the objectives for my experiment:

  • Make the Captivate SWF call the Actionscript function unload() (located in the Flash SWF) via Javascript
  • No variables would be passed… I wanted to keep things as simple as possible

I began editing the code from the aforementioned Adobe example, and in a few short minutes my proof-of-concept was up and running! I was amazed at how quick and easy it was… ExternalInterface was much easier to use than I anticipated.

Here’s what I did:

Step one: Add two lines of Actionscript code to the Flash container

import flash.external.ExternalInterface;
ExternalInterface.addCallback("unload", this, unloadSWF);Code language: JavaScript (javascript)

Step two: Add two small Javascript functions to the HTML page

//Detect Flash container movie
function getFlashMovie(movieName) {
   if (navigator.appName.indexOf("Microsoft") != -1) {
      return window[movieName];
   }
   else {
      return document[movieName];
   }
}

//Function to be called by Captivate
function captivateUnload() {
   //Calls "unload" method established in ExternalInterface Actionscript code
   getFlashMovie("simpleSwfLoader").unload();
}Code language: JavaScript (javascript)

Update: In my subsequent testing, using document.getElementById() works fine in place of the getFlashMovie() function. While some developers warn against using getElementById for grabbing Flash SWFs, my tests — using SWFObject as the embed method — were successful in the following environments: WinXP (FF2, FF3, IE6, IE7, Safari 3) and Mac OS X (Safari 3, FF3, Opera 9.5). You can still use getFlashMovie if you prefer.

Step three: Add the Javascript call captivateUnload() to the Captivate button.

That’s it! Really simple, huh? I must admit I was expecting it to be much more complicated.

How it works

As I mentioned earlier, I’m no expert on the ExternalInterface class, but I’ll try and explain what’s going on here. Let’s work backwards and start with the Javascript call in Captivate. Here are the source files if you’d like to use them.

The Captivate file

Captivate Button Options

In my example, I’m using a button to call a Javascript function named captivateUnload(). This Javascript function is located in the HTML file.

The HTML file

Moving to the HTML file, we see two functions. The first function, getFlashMovie(movieName), returns the Flash SWF as a Javascript object. This allows us to add functions to the SWF object. This is critical because Flash’s ExternalInterface class will listen for functions attached to the SWF object.

In my example, the Javascript function captivateUnload() attaches the Javascript function unload() to a SWF named simpleSwfLoader:

function captivateUnload() {
   getFlashMovie(simpleSwfLoader).unload();
}Code language: JavaScript (javascript)

By itself, this script won’t do much. In fact, it will throw an error in your browser if it doesn’t detect the accompanying ExternalInterface Actionscript code in your Flash SWF.

The Flash file

In the Flash file, the first line of Actionscript simply tells Flash to import the ExternalInterface class.

import flash.external.ExternalInterface;Code language: CSS (css)

The second line is where the action’s at (no pun intended… well, maybe a little!).

ExternalInterface.addCallback("unload", this, unloadSWF);Code language: JavaScript (javascript)

ExternalInterface’s addCallback method basically tells Actionscript to start listening for Javascript functions that have been attached to the SWF object. In our scenario, the unload() function has been attached to the SWF object.

The three parameters ("unload", this, unloadSWF) specify exactly what to listen for (a function named “unload”), where to listen (“this” SWF), and what to do when the SWF hears the correct bit o’ Javascript (execute a function named “unloadSWF”).

So for this example, the Flash SWF will execute function unloadSWF() when it hears Javascript call a function named unload() that is attached to a SWF named simpleSwfLoader.

Got all that? I know it sounds confusing (it still makes my head spin a little), but it’s actually pretty straightforward. Here’s a diagram for you visual learners:

Flow of scripting calls

And that’s the whole enchilada.

Let me remind you that this is a very simple example, and I’m sure this technique can be harnessed to do much more! Also, this example uses ExternalInterface for Actionscript 2.0. Flash CS3’s new Actionscript 3.0 uses slightly different code, but the concept is the same.

If any of you find this tutorial useful, or think of new ways to utilize this technique, please let me know by adding a comment.

Thanks, and good luck!

Embedded SWF technique source files  |  ExternalInterface technique source files

Similar Posts

9 Comments

  1. Nice tip, will definitely come in useful at some stage.

    For CD-ROM deployment I think we’re still stuck with the old method though, for the meantime.

  2. This method should work fine on a CD-Rom, as long as the files are being called from a web browser — the browser handles the scripting.

  3. I have a question, how can i access slide notes text of captivate inside a swf widget?

  4. Thanks for the tutorial and source files… very helpful!!!
    Using the embedded technique, does anybody know if there is a way to make the button reappear on the root flash movie after the captivate SWF has unloaded?

  5. @david

    edit the unloadSWF function to make the button visible again:

    //-- loadSlide --
    function loadSlide(swfName){
    
        //clear the swfHolder just to be safe.
        unloadSWF();
    
        //hide our lil' button
        btnLoadSwf.visible = false;
    	
        //load swf
        loadMovie(swfName,swfHolder);
    }
    
    //-- unloadSWF --
    function unloadSWF(){
        unloadMovie (swfHolder);
    
        //Make button visible again
        btnLoadSwf.visible = true;
    }
  6. Philip,
    I am building a captivate template in which I would like to use a swf with multiple background layouts.
    I would like to build a captivate variable that can be set on frame exit in order to tell the next slide what the variable is.
    AS2
    var pageTypePassed = _root.pageType;

    function passVariable(){
    if (pageTypePassed == 1){
    bg1_mc.visible = true;
    } else {
    bg1_mc.visible = false;

    }
    }
    _root.passVariable();

    I have tried this script within my embedded swf to see if I could hide/show within the captivate file, but it is not working.

    I am using an action in captivate to pass the value of 1 just to test out the communication between the two files. Any help would be greatly appreciated.

Comments are closed.