Tuesday, April 10, 2007

A closer Look at IFrames and ExternalInterface in Flex

Well I've been super busy as usual, working on some pretty cool stuff, still managing to only put food on the table but also sticking to my guns and doing only Flex projects. Of course, my blogging has suffered dearly.

In one of my current projects, I am faced with the challenge of somehow embedding IFrames inside my flex app, however without having any control over the html wrapper that my Flex app will sit in. Sure I could 'advise' users on adding the appropriate javascript functions and the hidden div element at the bottom of the body, as we've seen in other Flex Iframe examples out there. But I figured there had to be a way to do this without needing to rely on this external dependency.

As it turns out I can do inline Javascript function calls within my ExternalInterface.call, an example of this would be:

ExternalInterface.call("function(){return window.location.href;}");
which is a simple little function to quickly get the address of the current page. Another handy example is this one:

ExternalInterface.call("function(){document.location.href=document.location;}");
This is a quick way for a user to 'logout' of the flex application, it simply reloads the web page. So as you can see, we can do a great deal without ever needing to touch the html wrapper. So why not do the same thing with Iframe support? Here's what I came up with:
ExternalInterface.call("function(){" +
"var tempIFrame=document.createElement('div');" +
"tempIFrame.setAttribute('id','vyFrame');" +
"tempIFrame.style.position='absolute';" +
"document.body.appendChild(tempIFrame);" +
"tempIFrame.style.backgroundColor='transparent';" +
"tempIFrame.style.border=0;" +
"tempIFrame.style.visibility='hidden';}");
I am going to try and create a div element in memory and append it to the DOM. It's best to run this code during the application preinitialize() event. However, before I move on to explain the rest I just want to mention that I did run into some issues here with IE6. As you'll notice I appendChild and then I set the styles. Turns out that if I manipulate a new element too much prior to adding it to the DOM, it just gets all flaky and will not work. Believe me, this was not an easy bug to track down!

Now, we need to change the remaining externalInterface calls as well... things like move, hide, show and source. Building on the example at deitte.com that first got me started ages ago, I changed all the calls as they are found in his IFrame.mxml component:

ExternalInterface.call("moveIFrame",globalPt.x ,
globalPt.y, this.width, this.height);
Becomes:
ExternalInterface.call("function(){" +
"var frameRef=document.getElementById('vyFrame');" +
"frameRef.style.left=" + globalPt.x + ";"+
"frameRef.style.top=" + globalPt.y + ";" +
"var iFrameRef=document.getElementById('vyIFrame');" +
"iFrameRef.width=" + this.width + ";" +
"iFrameRef.height=" + this.height + ";}");
ExternalInterface.call("loadIFrame", source);
Becomes:
ExternalInterface.call("function(){vyFrame.innerHTML='" +
"<iframe id=\"vyIFrame\" src=\"" + source +
"\" frameborder=\"0\"></iframe>';}");
ExternalInterface.call("showIFrame");
Becomes:
ExternalInterface.call("function(){" +
"document.getElementById('vyFrame').style.visibility='visible';}");
ExternalInterface.call("hideIFrame");
Becomes:
ExternalInterface.call("function(){" +
"document.getElementById('vyFrame').style.visibility='hidden';}");

After a bit of testing I did manage to get this to work, sort of. I ended up spending some time playing with the compiler options in Flexbuilder. I found that if I turned off history management, my Iframes wouldn't appear. However removing the script reference to history.js seemed to have no real effect. For those of you that are not Flash-savvy :) it turned out that the only things to really watch out for, and again I am at the mercy of the users who embed my flex app into their html pages, is that the AC_FL_RunContent() has a final parameter setting of "wmode", "opaque" and the object embed tag also contains wmode="opaque". If this setting is not there or set incorrectly the Iframe will appear behind your flash player.

7 comments:

Anonymous said...

Hello !
Thank you for this very interesting post. I also have to use IFrame, with all the problems it brings... And I always have to switch between the wrapper side and the actionscript side, which is quite bad for maintainability. So your idea seems a very good solution to me.
By the way, did you notice any performance issue with your method (compared to the usual way)?

Vic Rubba said...

To be honest I actually figured this all out last night just before I blogged it. The few places that I did replace previous externalinterface calls with the new inline ones, I didn't notice much difference. One thing that seemed to be different though is that if the object wasn't successfully added to the DOM by the time the IFrame.mxml was created, it wouldn't work, which is why I recommended making that call during the preinit event of the application.

Adobe Air/Flex Tips & architecture said...

Just a note. iframes on top of flash just don't work in safari/firefox on the mac. Tried everything but it seems to be a browser bug (according to other blogs)

btw nice articles on your blog!

Arnoud

Anonymous said...

Thanks for the post! Just a quick comment: since ExternalInterface.call takes a function, you could rewrite

ExternalInterface.call("function(){return window.location.href;}");

as

ExternalInterface.call("window.location.href.toString");

Unknown said...

Has anyone had any experience with getting ExternalInferface to work with SWF’s that are embedded in PDF documents.

ExternalInterface.available returns true, but I haven’t found the way to call a function exposed by the addCallback() method.

[P.S. I realize that this is a little off-topic, but I have had a lot of trouble tracking down discussions relating to the use of Flash inside PDF docs]

Thanks!

Terry.

Shane said...

Very informative Thank YOU!

Expect Miracles...
Shane

Shane said...

Very informative Thank YOU!

Expect Miracles...
Shane