Internet Explorer y su incompatibilidad con applets y params (_cx, _cy)
Últimamente he tenido problemas en mi trabajo con Internet Explorer y su tratamiento de la propiedad innerHTML de los objetos "applet".
El problema era el siguiente: teniendo un nodo HTML, ¿Cómo puedo convertir una string que contenga código HTML en un objeto Javascript, y que además sea compatible con cualquier navegador?
La solución es bien sencilla:
function createObject(objectCode) {
var container = document.createElement('span'); // Creamos un contenedor
container.innerHTML = objectCode; // Hacemos que su innerHTML sea el que le hemos pasado a la función
return container.firstChild; // Retornamos el primer hijo del contenedor.
}
Si ejecutamos el siguiente código, podremos ver que funciona, tanto en Firefox como en IE:
var object = createObject('<a href="http://www.google.es">Google</a>');
document.appendChild(object);
Se supone que el código anterior crea un objeto "a" (un link) dinámicamente a partir de su HTML y lo añade al document correctamente.
Veamos ahora el resultado de ejecutar lo siguiente en Firefox:
var object = createObject('
<applet codebase="http://loquesea/" archive="lol.jar" code="Clase">
<param name="parametro1" value="valor1"/>
<param name="parametro2" value="valor2"/>
</applet>
');
document.appendChild(object);
¿Funciona, verdad? Este código crea un objeto APPLET a partir de su HTML y lo añade al document correctamente, con todos sus parámetros intactos (fijaos en el parametro1 y el parametro2).
¿Pero qué pasa si intentamos ejecutar este código en Internet Explorer? Pues el resultado es que Internet Explorer se come los PARAM, los borra, y en su lugar introduce dos parámetros desconocidos, "_cx" y "_cy". Intentadlo y lo comprobareis.
Esto pasa justo al setear el innerHTML del container. Si hacemos dos alerts lo comprobaremos:
function createObject(objectCode) {
var container = document.createElement('span'); // Creamos un contenedor
alert(objectCode);
container.innerHTML = objectCode; // Hacemos que su innerHTML sea el que le hemos pasado a la función
alert(container.innerHTML);
return container.firstChild; // Retornamos el primer hijo del contenedor.
}
No sé qué más cosas parsea IE al setear un innerHTML, pero para solucionar este caso, he hecho una simple función que hace lo siguiente:
- Modificando el código HTML de entrada, sustituye los objetos APPLET por objetos DIV con un atributo "specialObject" seteado como "specialApplet". He elegido el objeto DIV por el hecho de que puede contener objetos BR (los utilizaremos en el paso 2).
- Modificando el código HTML de entrada, sustituye los objetos PARAM por objetos BR con un atributo "specialObject" seteado como "specialParam". He elegido el objeto BR por el hecho de que no admite ningún objeto en su interior y el explorador suele "autocerrarlo". Esto nos evitará tener que hacer más sustituciones en el código HTML y en el árbol DOM.
- Creo un SPAN llamado "container" y seteo su innerHTML con el código HTML modificado.
- Recurisvamente, navego por los hijos de container y hago lo siguiente:
- Si el nodo tiene un atributo "specialObject" seteado como "specialParam":
- Creo un nuevo objeto PARAM dinámicamente y le asigno cada uno de los atributos del objeto original.
- Reemplazo el objeto original por este nuevo PARAM en el padre.
- Si el nodo tiene un atributo "specialObject" seteado como "specialApplet":
- Creo un nuevo objeto APPLET dinámicamente y le asigno cada uno de los atributos del objeto original.
- Por cada hijo del objeto original:
- Aplico la función recursiva.
- Si el hijo es un PARAM, lo añado al applet creado.
- Reemplazo el objeto original por este nuevo APPLET en el padre.
- Si no es ninguno de los anteriores:
- Aplico la función recursiva sobre cada uno de sus hijos.
- Devuelvo el primer hijo del container.
/**
* Creates new object using its html code.
* @param string objectCode
* @return object
*/
function createObject(objectCode) {
// Internet Explorer can't include "param" tag when is setting an innerHTML property.
objectCode = objectCode.split('<applet ').join('<div specialObject="specialApplet" ').split('<APPLET ').join('<div specialObject="specialApplet" '); // Is a 'div' because 'div' object can contain any object.
objectCode = objectCode.split('</applet>').join('</div>').split('</APPLET>').join('</div>');
objectCode = objectCode.split('<param ').join('<br specialObject="specialParam" ').split('<PARAM ').join('<br specialObject="specialParam" '); // Is a 'br' because 'br' can't contain nodes.
objectCode = objectCode.split('</param>').join('</br>').split('</PARAM>').join('</br>');
var container = document.createElement('span');
container.innerHTML = objectCode;
function recursiveParamsFix(object) {
if (object.getAttribute && object.getAttribute('specialObject') == 'specialParam') {
var param = document.createElement('param');
for (var i = 0; i < object.attributes.length; ++i) {
if (object.attributes[i].nodeValue !== null) {
param.setAttribute(object.attributes[i].nodeName, object.attributes[i].nodeValue);
}
}
// IE fix
if (param.NAME) {
param.name = param.NAME;
param.value = param.VALUE;
}
param.removeAttribute('specialObject');
object.parentNode.replaceChild(param, object);
}
else if (object.getAttribute && object.getAttribute('specialObject') == 'specialApplet') {
var applet = document.createElement('applet');
for (var i = 0; i < object.attributes.length; ++i) {
if (object.attributes[i].nodeValue !== null) {
applet.setAttribute(object.attributes[i].nodeName, object.attributes[i].nodeValue);
}
}
applet.removeAttribute('specialObject');
for (var i = 0; i < object.childNodes.length; ++i) {
recursiveParamsFix(object.childNodes[i]);
if (object.childNodes[i].nodeName == 'param' || object.childNodes[i].nodeName == 'PARAM') {
applet.appendChild(object.childNodes[i]);
--i; // When we inserts the object child into the applet, object loses one child.
}
}
object.parentNode.replaceChild(applet, object);
object = applet;
}
else {
for (var i = 0; i < object.childNodes.length; ++i) {
recursiveParamsFix(object.childNodes[i]);
}
}
}
recursiveParamsFix(container);
return container.firstChild;
}
