Getting the SWF’s HTML object/embed id from within the Flash movie itself

Update: if you’re using swfobject to embed the SWF, this post may still be useful, but there’s some better, simpler alternatives to finding the SWF element id. Read the comments to read about them. The original article follows, and is suitable for all situations in which you have a SWF embedded in a page.

In ActionScript, it’s quite common that you need to talk to the JavaScript/HTML side and then have JS/HTML talk back to Flash. That is easily accomplished by ExternalInterface, but one of the caveats is that when talking from the JS/HTML side to the SWF side you must know the element id of the SWF you’re trying to communicate to. You then do,

document.getElementById("myFlashMovie").myCallbackFunction();

I ran into a problem today, however, when I wanted to call a JavaScript function from Flash and have the function call my movie back at a later time – without having the SWF id hard-coded anywhere else (e.g., without embedding it on the AS3 code or using a FlashVars parameter). The reason is, I wanted to have a self-contained function that would open a popup function and to have the HTML page tell me when that window was closed. Therefore, the JS side needed to know the SWF id in advance.

At a first glance, the ExternalInterface offers the objectID property, which is supposed to do exactly that. Unfortunately, however, this property isn’t as reliable as the documentation makes it sound – on a website I was testing, while embedding the SWF with SWFObject, the property’s value wasn’t being set on Google Chrome (and, I assume, all other plugin-based browsers). This is probably due to the way the embedding was done (even though I used the same name as the SWF object’s name and the attribute parameter’s id):

// Embeds SWF
var flashvars = {};

var params = {};
params.allowFullScreen = "true";
params.allowScriptAccess = "always";
params.allowNetworking = "all";
params.base = ".";

var attributes = {};
attributes.id = "mainMovie";

swfobject.embedSWF('index.swf', attributes.id, '100%', '100%', '10.0.0', 'expressinstall.swf', flashvars, params, attributes);

Miller Medeiros warned me of that issue, and suggested an alternative – looping through all object and embed elements on the page, checking whether an arbitrary test variable (created previously) existed. This is the solution implemented on his code here, albeit for a different purpose.

The perfect The most flexible solution to find the SWF name from inside Flash, then, is as such (as a self-contained JavaScript function injected from AS3):

public function getSWFObjectName(): String {
	// Returns the SWF's object name for getElementById

	// Based on https://github.com/millermedeiros/Hasher_AS3_helper/blob/master/dev/src/org/osflash/hasher/Hasher.as
			
	var js:XML;
	js = <script><![CDATA[
		function(__randomFunction) {
			var check = function(objects){
       				for (var i = 0; i < objects.length; i++){
        				if (objects[i][__randomFunction]) return objects[i].id;
       				}
       				return undefined;
       			};
       		
      	 		return check(document.getElementsByTagName("object")) || check(document.getElementsByTagName("embed"));
		}
	]]></script>;
			
	var __randomFunction:String = "checkFunction_" + Math.floor(Math.random() * 99999); // Something random just so it's safer
	ExternalInterface.addCallback(__randomFunction, getSWFObjectName); // The second parameter can be anything, just passing a function that exists
			
	return ExternalInterface.call(js, __randomFunction);
}

When called, this function creates a callback to a random function (that is never called), and then calls some JavaScript code that checks all object and embed elements in the page to see if they have any reference to that same random function. When they do, they return the element’s name – that’s the SWF movie.

This behavior is also similar to this solution suggested by Flavio Caccamo, although at first I overlooked the article thinking it was too specific to ActionScript 2.

The popup-with-callback function, therefore, looks like this:

public function openPopup(__url:String, __width:int = 600, __height:int = 400, __name:String = "_blank", __onClosed:Function = null): void {
	// Open a popup window, with optional callback when closed

	var js:XML;
	js = <script><![CDATA[
		function(__url, __width, __height, __name, __SWFContext, __onClosed) {
					
			if (__onClosed != "") {
				// If 'onClosed' is supplied, call a function when the popup window is closed
					
				var checkForWindow = function() {
					if (newWindow.closed) {
						clearInterval(windowCheckInterval);
						document.getElementById(__SWFContext)[__onClosed]();
					}
				};

				var windowCheckInterval = setInterval(checkForWindow, 250);
			}
					
			var wx = (screen.width - __width)/2;
			var wy = (screen.height - __height)/2;

			var newWindow = window.open(__url, __name, "top="+wy+",left="+wx+",width="+__width+",height="+__height);
			if (newWindow.focus) newWindow.focus();
					
		}
	]]></script>;
			
	var __onClosedString:String = "";
			
	if (Boolean(__onClosed)) {
		__onClosedString = "checkFunction_" + Math.floor(Math.random() * 99999); // Something random just so it's safer
		ExternalInterface.addCallback(__onClosedString, __onClosed);
	} 
			
	ExternalInterface.call(js, __url, __width, __height, __name, getSWFObjectName(), __onClosedString);
}

I haven’t tested on absolutely every version of every browser yet, but it has been enough to get it work on my two main test environments (Google Chrome, and Internet Explorer).

14 responses

  1. The inline XML with the script element isn’t displayed when I read this post (in Safari), so the displayed code has just js = ;

  2. I think the name of the callback should contain an UID instead of a random value otherwise every time that you call the method, a new callback is added.

  3. @Mark: Nice; that seems to work. I was under the impression having the attribute’s name be the same as the id would create some kind of conflict (because name would be some wrapper element), but getElementById seems to work fine. I like the idea of having a function that works regardless of the way attributes are set, but if objectID is that reliable across all browsers/systems, than the function on the first block of code above is not as necessary.

    @Fabio: the above code is a little bit simplified just to serve as an example; on the production code I’m using, getSWFObjectName() only does the check once, storing the name of the SWF and reusing it later on the next calls, so a UID for the function isn’t necessary. Cleaner than additional JS calls or creating new functions.

    public static function getSWFObjectName(): String {
    	if (Boolean(_SWFName)) return _SWFName;
    	(...code here...)
    	_SWFName = ExternalInterface.call(js, __randomFunction);
    	return _SWFName;
    }
  4. hello people ^^

    there is a more simple way to access the the flash html ID. you just need to call in javascript with External Inteface the propertie “this.attributes.id”.

    For exemple :

    public static function getSWFObjectName(): String {
    return ExternalInterface.call(“function(){return this.attributes.id;}”);
    }

    Easy, no? ^^

    When I have to change something on the html container, I simply use something like this :

    public static function set containerHeight($height:int):void{
    ExternalInterface.call(“function(){document.getElementById(this.attributes.id).style.height = ‘” + $height + “px’}”);
    }

  5. @Guillaume: `this.attributes.id` only works because `this` inside the `ExternalInterface.call` points to the `window` object (global scope) and since you declared the global variable `attributes` to pass to swfobject it is accessing that object instead of the HTMLElement itself.. If you have 2 different swfs on the same page, or if you are not setting an ID (swfobject uses the container ID by default) or if the attributes object is on a different scope (i.e. inside a function call) it won’t work…

    function embedFlash(){
      //attributes is not on the global scope anymore so you can't access it
      var attributes = {id:'lorem-ipsum'};
      swfobject.embedSWF('index.swf', attributes.id, '100%', '100%', '10.0.0', 'expressinstall.swf', null, null, attributes);
    }
    embedFlash();

    I wouldn’t rely on the existence of global variables specially on one that is so generic and that has a big chance of being overwritten.

    @Mark: Setting the `name` attribute works because of the way that swfobject embeds flash… I usually avoid using the name attribute (on any element) since it has some bugs on IE 6-7 where it confuses name with id (I think swfobject already fixes this though).

    Another thing that is good to note is that if you are using the “static embed” of swfobject and/or other embed codes (like old versions of swfobject) you have 2 nested object elements (or `embed` tag depending on the case) and the one that has the ExternalInterface callback methods is the inner one on all browsers besides IE and the ID is on the outer one.. so `getElementById(‘my_id’).myAwesomeCallback()` sometimes doesn’t work properly because of that.. To make sure you are calling the proper movie you have to check if the element contain the desired method and if not you search on the child nodes for it… or you could store a reference to the actual element that contain the callbacks instead of the element ID. – that’s one of the reasons why I only use swfobject dynamic publishing, it avoid this kind of bugs and gives way more control..

    cheers.

  6. @Eric Priou:

    I never tried, but I can’t see why it wouldn’t – as long as the SWF is focusable via JS (apparently FireFox has some issues with this?) and an user-initiated event is not required for it to work.

    In that case, just calling

    ExternalInterface.call("document.getElementById('" + getSWFObjectName() + "').focus");

    Should do the trick.

  7. Hi there,

    I disagree with you about “ExternalInterface offers the objectID property, which is supposed to do exactly that. Unfortunately, however, this property isn’t as reliable as the documentation makes it sound”.

    In documentation it’s said “Returns the id attribute of the object tag in Internet Explorer, or the name attribute of the embed tag in Netscape.”
    So you need to set both “id” and “name” to be able to use “ExternalInterface.objectID”.

    But when SWFObject is used to embed an SWF object, you must to set also “name” attribute, else only “id” is set and all SWF in NPRuntime supported browser will had a null “ExternalInterface.objectID”

    http://memmie.lenglet.name/documents/lab/info/
    Here a test without SWFObject and you got in “[Container] > Object identifier”
    in all browsers (i tested: FF, Safari, Chrome, Opera) the value “infoReader”.

Comments are closed.