05 diciembre 2008

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:
  1. 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).
  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.
  3. Creo un SPAN llamado "container" y seteo su innerHTML con el código HTML modificado.
  4. Recurisvamente, navego por los hijos de container y hago lo siguiente:
    1. Si el nodo tiene un atributo "specialObject" seteado como "specialParam":
      1. Creo un nuevo objeto PARAM dinámicamente y le asigno cada uno de los atributos del objeto original.
      2. Reemplazo el objeto original por este nuevo PARAM en el padre.
    2. Si el nodo tiene un atributo "specialObject" seteado como "specialApplet":
      1. Creo un nuevo objeto APPLET dinámicamente y le asigno cada uno de los atributos del objeto original.
      2. Por cada hijo del objeto original:
        1. Aplico la función recursiva.
        2. Si el hijo es un PARAM, lo añado al applet creado.
      3. Reemplazo el objeto original por este nuevo APPLET en el padre.
    3. Si no es ninguno de los anteriores:
      1. Aplico la función recursiva sobre cada uno de sus hijos.
  5. Devuelvo el primer hijo del container.
Una vez explicado el algoritmo, aquí teneis la función de marras, compatible con Firefox e IE (no lo he probado en más navegadores, pero supongo que funcionará):
/**
* 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;
}

01 diciembre 2008

Térmens LAN Party

Copy-paste de mi colega _TuXeD_:
 
Los dias 12, 13, 14 de diciembre en Térmens (a 15km de Lérida), se celebra la Térmens LAN Party. En esta quinta edición apostamos por el software libre, por eso se han organizado una serie de ponencias muy Interesantes.
 
  • Presentación de Hispalinux. Ponente: Jorge Fuertes, Presidente de Hispalinux.
  • Software de gestión para pymes: Bulmages. Ponente: Tomeu Borrás, Main Developer.
  • Lo que nos cuestan las multinacionales. Ponente: Réne Mérou.VOIP y software libre. Ponente: Ramón Martínez de morrotel.com
  • Grub avanzado para GNU/Linux y otros SOs. Ponente: Adrian15 de Super Grub Disk.
  • Cracking de redes wireless. Ponent: Pol Colell.
  • DD-WRT y Redes Libres. Ponents: rorito i knoppix de Guifi.net.
  • KDE EDU y KDE 4. Ponentes: Aleix Pol (Kalgebra) y Albert Astals Cid (oKular).

Además, este año colaboramos con la Marató de TV3 y parte del dinero de las inscripciones se destinarán a esta fundación para el estudio de las enfermedades mentales graves. Con lo que prácticamente tenemos garantizada la presencia de la TV, además de diferentes medios de prensa habituales.