IE ActiveX Change
"Click to activate and use this control"

Sunday, April 23, 2006

Using Comments Solution

This solution is very similar to the <div> solution published earlier, however the original <div> solution had a few problems due to the fact that IE would request the swf twice, once for the hidden div, and again when the JavaScript wrote the contents back out again. It also sometimes caused IE to think it was still loading an item, even though the page had finished loading. This solution gets around the problem by preventing IE loading the initial swf by using comments.

First of all create a JavaScript file called, say, ieupdate.js, containing the following code:

var bo_ns_id = 0;

function startIeFix(){
if(isIE()){
document.write('<div id="bo_ns_id_' + bo_ns_id + '"><!-- ');
}
}

function endIeFix(){
if(isIE()){
document.write('</div>');
var theObject = document.getElementById("bo_ns_id_" + bo_ns_id++);
var theCode = theObject.innerHTML;
theCode = theCode.substring(4 ,9+theCode.indexOf("</object>"))
document.write(theCode);
}
}

function isIE(){
// only for Win IE 6+
// But not in Windows 98, Me, NT 4.0, 2000
var strBrwsr= navigator.userAgent.toLowerCase();
if(strBrwsr.indexOf("msie") > -1 && strBrwsr.indexOf("mac") < 0){
if(parseInt(strBrwsr.charAt(strBrwsr.indexOf("msie")+5)) < 6){
return false;
}
if(strBrwsr.indexOf("win98") > -1 ||
strBrwsr.indexOf("win 9x 4.90") > -1 ||
strBrwsr.indexOf("winnt4.0") > -1 ||
strBrwsr.indexOf("windows nt 5.0") > -1)
{
return false;
}
return true;
}else{
return false;
}
}

This will then need to be included within the <head> tags for each page:
<script type="text/javascript" src="ieupdate.js"></script>


Then you need to include a line directly before, and directy after each <object> tag:
<script type="text/javascript">startIeFix();</script>
<object ...
etc etc
</object>
<!-- --><script
type="text/javascript">endIeFix();</script>


Note: Although the empty comment tag doesn't seem to do anything, it is necessary to get the closing comment tag (-->), otherwise once IE has written out the starting comment tag it will treat everything as a comment until it reaches a closing comment.

As the code is extracted from the comments the swf file isn't requested nor loaded until it is rewritten by the external JavaScript.

Friday, April 21, 2006

Rewrite div solution

This solution is very similar to the <noscript> solution published earlier, but my original version using <divs> didn't work if you used FlashVars parameters. However, thanks to a suggestion in the Flash Forums from hemels I've updated to JavaScript to workaround the problem.

First of all create a JavaScript file called, say, ieupdate.js, containing the following code:

var bo_ns_id = 0;

function startIeFix(){
if(isIE()){
document.write('<div style="display: none;" id="bo_ns_id_' + bo_ns_id + '">');
}
}

function endIeFix(){
if(isIE()){
document.write('</div>');
var theObject = document.getElementById("bo_ns_id_" + bo_ns_id++);
if(theObject.firstChild.data){
theObject.firstChild.removeAttribute('data');
}
var theParams = theObject.getElementsByTagName("param");
var theParamsLength = theParams.length;
for (var j = 0; j < theParamsLength; j++) {
if(theParams[j].name.toLowerCase() == 'flashvars'){
var theFlashVars = theParams[j].value;
}
}
var theInnnerHTML = theObject.innerHTML;
var re = /<param name="FlashVars" value="">/ig;
theInnnerHTML = theInnnerHTML.replace(re, "<param name='FlashVars' value='" + theFlashVars + "'>");
theObject.outerHTML = theInnnerHTML;
}
}

function isIE(){
var strBrwsr = navigator.userAgent.toLowerCase();
if(strBrwsr.indexOf("msie") > -1 && strBrwsr.indexOf("mac") < 0){
return true;
}else{
return false;
}
}

This will then need to be included within the <head> tags for each page:
<script type="text/javascript" src="ieupdate.js"></script>


Then you need to include a line directly before, and directy after each <object> tag:

<script type="text/javascript">startIeFix();</script>
<object ...
etc etc
</object>
<script type="text/javascript">endIeFix();</script>

And that is it! The code checks for IE on a PC, and so doesn't affect any other browsers, the <object> is enclosed in a div with its display style set to 'none', and so doesn't run until the external javscript writes it back out (therefore doesn't cause any double running issues), and the code still maintains any FlashVars too!

Thursday, April 20, 2006

Simple Write Out (With optional RegEx)

An alternative to all the official solutions is instead of passing through a list of parameters and attributes for the width, height, bgcolor etc which the receiving JavaScript function then needs to join together again, is to simply pass the entire <object> to a JavaScript function that writes it back out to the screen.

The JavaScript function for this is very simple:
function ieoutput(theText){
document.write(theText);
}

The tricky part comes with replacing the <object> tag with a call to the JavaScript function, and including it in a <noscript> tag too, to allow for browsers with javaScript disabled.

So that the final code looks like:

<script type="text/javascript">echocontent('<object ...' +
'your code' +
'your code' +
'</object>')
</script>
<noscript>
<object ...
your code
</object>
</noscript>

As a short cut, we can use the Regular Expression Search and Replace feature of TextPad.

For the normal 7 lines of HTML code for Flash we can search for:
\(.*\)\n\(.*\)\n\(.*\)\n\(.*\)\n\(.*\)\n\(.*\)\n\(.*\)


And replace with:
<script type="text/javascript">ieoutput('\1'
\+\n '\2' \+\n '\3' \+\n '\4' \+\n '\5' \+\n '\6' \+\n
'\7')\n</script>\n<noscript>\n&\n</noscript>


If you have a different number of lines in your <object> the Regular Expression can be changed accordingly.

<noscript> solution

In the Macromedia Forums, fpproductions came up with a clever workaround using the <noscript> tags. The solution works by getting IE to add a <noscript> just before the object, and a </noscript> just after the object, and we can then use JavaScript to read the contents of the <noscript> tag, and write it back out again. I changed fpproductions original script for that the object tag is written out as soon as IE processes each object tag.

Firstly create a JavaScript file called, say, ienoscript.js containing:


var bo_ns_id = 0;

function startIeFix(){
if(isIE()){
document.write('<noscript id="bo_ns_id_' + bo_ns_id + '">');
}
}

function endIeFix(){
if(isIE()){
var theObject = document.getElementById("bo_ns_id_" + bo_ns_id++);
var theNoScript = theObject.innerHTML;
document.write(theNoScript);
}
}

function isIE(){
var strBrowser = navigator.userAgent.toLowerCase();
if(strBrowser.indexOf("msie") > -1 && strBrowser.indexOf("mac") < 0){
return true;
}else{
return false;
}
}

This file is referenced inbetween the <head> and </head> tags:

<script type="text/javascript" src="ienoscript.js"></script>

Then, for each <object> tag you need to add a line above and below each one.


<script type="text/javascript">startIeFix();</script>
<object
etc etc
</object>
<!--[if gte IE 6]></noscript><![endif]--><script
type="text/javascript">endIeFix();</script>

It seems to work OK on all the browsers I tried it on, the only exception being Opera when it identifies itself as IE, which I guess could be solved using the [if gte IE 6] to write the top <script> bit out too. Something like:

<!--[if gte IE 6]><script type="text/javascript">startIeFix();</script><![endif]-->
<object
etc etc
</object>
<!--[if gte IE 6]></noscript><script
type="text/javascript">endIeFix();</script><![endif]-->


Note:
This technique also preserves any FlashVars parameters too.

External JavaScript solutions

The problems with all the official solutions (see links in the right hand column), is that they all require you to change your existing HTML pages. Wouldn't it be great if you could make one simple change to your site and all your pages would work again?

Well, in theory at least, you can. Any tag that is written to the page through an external script is not affected by the IE update, and so we can use an external JavaScript file to loop through and replace all the instances of <object> tags, and replace them with themselves. The basic code for this is:

theObjects = document.getElementsByTagName("object");
for (var i = 0; i < theObjects.length; i++) {
theObjects[i].outerHTML = theObjects[i].outerHTML;
}

Unfortunately there are a few problems with this solution. For a start some browsers (such as IE on a Mac) have trouble with the code, and outerHTML is an IE only property. This can easily be fixed with a check for the platform/browser.

Another issue is that if you use the data attribute in the <object> tag (such as in the Flash Satay method) then for some reason IE doesn't see any of the parameters, but again this can be overcome with a bit of JavaScript to remove the attribute, as IE doesn't use it anyway, it is only there for other browsers.

More troublesome though is the fact that IE tends to remember the original <object>, and this can cause issues such as increased memory/cpu usage, especially as IE doesn't always seem to remove the original <object> when leaving the page.

In the Macromedia Forums, fpproductions came up with a simple solution of including an window.onunload event, that kills the <object> by setting the outerHTML to an empty string.

Combining these additions together we have:

function ieupdate(){
var strBrowser = navigator.userAgent.toLowerCase();
if(strBrowser.indexOf("msie") > -1 && strBrowser.indexOf("mac") < 0){
var theObjects = document.getElementsByTagName('object');
var theObjectsLen = theObjects.length;
for (var i = 0; i < theObjectsLen; i++) {
if(theObjects[i].outerHTML){
if(theObjects[i].data){
theObjects[i].removeAttribute('data');
}
var theParams = theObjects[i].getElementsByTagName("param");
var theParamsLength = theParams.length;
for (var j = 0; j < theParamsLength; j++) {
if(theParams[j].name.toLowerCase() == 'flashvars'){
var theFlashVars = theParams[j].value;
}
}
var theOuterHTML = theObjects[i].outerHTML;
var re = /<param name="FlashVars" value="">/ig;
theOuterHTML = theOuterHTML.replace(re,
"<param name='FlashVars' value='" + theFlashVars + "'>");
theObjects[i].outerHTML = theOuterHTML;
}
}
}
}

window.onunload = function() {
if (document.getElementsByTagName) {
var objs = document.getElementsByTagName("object");
for (i=0; i<objs.length; i++) {
objs[i].outerHTML = "";
}
}
}


Note: The character in in the above code means that the code should run on one continuous line.

All that remains to do is place a call to the ieupdate somwhere on the page, now the easiest way would to include it in the window.onload event, however this is triggered when the page has finished loading, which may cause the Flash movies to play for a few seconds and then be replaced. Therefore placing a call to the ieupdate function just before the </body> is probably the best bet.
<script type="text/javascript">ieupdate();</script>


Outstanding Issues
If you use FlashVars parameter then for some reason IE doesn't see it as part of the outerHTML, and therefore isn't carried over to the rewritten object. You can get around it by passing through the information as a querystring on the swf, but we are trying to avoid altering the HTML at all!


I've updated the code to maintain any FlashVars set in the <param> tags.


In other articles we'll be looking at alternative solutions, that while requiring changes to the HTML code are very minor, especially when compared to the official solutions.

Microsoft / Eolas Patent fight timeline

Although this change to IE appears to have come out of nowhere it has in fact been a long running battle between Eolas and Microsoft, these news stories, from The Register, show its progress: