Saturday, October 3, 2009

Accessing Flex-Object via Javascript

About :: Firebug 'is not a function'-Error
Problem :: Call to function before object is fully loaded
Solution :: Delay call until object available


I wanted to embed a flex-object into a web-page and access it via Javascript with the purpose of setting/getting/managing so called 'local shared objects'.

Everything worked fine during the tests, but in the live environment firebug1 threw me an error telling me that getLSO(), which is a function in my flex-object, is actually not a function. That was weird, because the test- and live-environments were exactly the same (same files, only in different folders).

I have been struggling with this problem almost half a day. And now that the problem is solved, I decided to share my insight.
I give you the code first; only the parts that are relevant to the topic at hand but enough that it sums up to a working example, keeping it clipped and clear. Then I'll describe the problem and the solution.


The Code


The flex-file, lso.mxml, looks essentially like this:

   1:  <?xml version="1.0"?>
   2:  <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" initialize="initApp()" >
   3:  <mx:Script>
   4:  <![CDATA[
   5:  import flash.external.*;
   6:  public var so:SharedObject;
   7:   
   8:  <!-- Determine, which functions will be accessible through external calls. -->
   9:  public function initApp():void {
  10:      if (ExternalInterface.available) {
  11:          ExternalInterface.addCallback("getLSO", getLSO);
  12:      }  
  13:  }
  14:  public function getLSO() :String {
  15:      so = SharedObject.getLocal("label");
  16:      return so.data.text;
  17:  }
  18:  ]]>
  19:  </mx:Script>
  20:  </mx:Application>
Figure 1. lso.mxml

The compiling of this file results in the file lso.swf2, which we embed in the file lso.html, directly after the body-tag, in the following way:

   1:  <body onLoad="initPage();">
   2:    <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="" id="FlexObject">
   3:      <param name=movie value="lso.swf">
   4:      <embed play=false swliveconnect="true" name="FlexObject" 
   5:        src="lso.swf" quality=high bgcolor=#FFFFFF 
   6:        width="0" height="0" type="application/x-shockwave-flash" 
   7:        ....>
   8:      </embed >
   9:    </object >
  10:    <!-- Further HTML-Code -->
  11:  </body>
Figure 2. lso.html :: Embedded Flex-bject

The Javascript code to acsess this flex-object and call one of its functions, looks like the following:

   1:  <script type="text/javascript">
   2:  function getFlashMovieObject(movieName)
   3:  {
   4:    if (window.document[movieName]) 
   5:        return window.document[movieName];
   6:    if (navigator.appName.indexOf("Microsoft Internet")==-1) {
   7:      if (document.embeds && document.embeds[movieName])
   8:        return document.embeds[movieName]; 
   9:    }
  10:    else {
  11:      return document.getElementById(movieName);
  12:    }
  13:  }
  14:  function initPage() {
  15:    var userID;
  16:    var flexObject = getFlashMovieObject("FlexObject");
  17:    var lso = flexObject.getLSO();
  18:    if(lso != null) userID = lso;
  19:    else userID = 0;
  20:  }
  21:  </script>
Figure 3. lso.html :: Javascript functions to access Flex-Object



The Problem


As said before, while testing there were no problems. After moving everything into the live-environment and removing all the redundant comments and alerts, firebug threw the following error:


Figure 4. Firebug :: Error Message


I had no clue why the function was suddenly not accessible anymore, since I didn't change anything essential. After some research and some experimenting I found out that the script worked fine when I called alert() prior to getLSO().

To cut a long story short, it turned out that the function getLSO() was invoked before the swf-file was fully loaded, and hence was not available. The alert()-call gave the swf-file enough time to load.



The Solution


The function initPage() was being initiated after the page is fully loaded (see Fig.2, Line1). But apparently that does not ensure that the swf-file in the document is fully loaded as well.
We couldn't find a way to determine the point in time/code where the loading of the flex-object is finished. I thought of calling the initPage() function with a certain time delay. This would be done like this:

   1:  <body  onload="setTimeout('initPage();', 1000);">
   2:  <!-- This will delay the execution for 1 second -->
Figure 5. lso.html :: Fixed Time-Delay

But that would not be an elegant solution, since the loading-time will differ from user to user, and we can't make sure that the time delay is enough for the slowest connection.
We ended up using an additional Javascript function:

   1:  <script language="javascript" type="text/javascript">
   2:  function isLoaded() {
   3:    var fo = getFlashMovieObject("FlexObject");
   4:    try {
   5:      var lo = fo.getLSO();
   6:      initPage();
   7:    }
   8:    catch(err) {
   9:      setTimeout('isLoaded();',200);
  10:    }
  11:  }
  12:  </script>
Figure 6. Time-Delay Until Object is Available

This functions catches the error and, after a pause of 0.2 seconds, calls itself again. As soon as getLSO() is available, initPage() is called.
Now let's replace line 1 in Fig.2 with the following:

   1:  <body onload="isLoaded();">


and we're done...



1 Highly recommended: Firebug for Firefox
2 You may want to download the Flex 3.4 SDK (free), unzip it to a convenient location, and then use the compiler mxmlc.exe in the bin-folder.
3 To format your code for your web-page, you may want to use this Formatter