setTimeOut y el Internet Explorer [JavaScript]

Ultimamente he estado escribiendo bastante código JavaScript para un Framework CSS/JS que estoy desarrollando, sobré el cual  hablaré más adelante. Como no podía ser de otra forma, Internet Explorer me dió dolores de cabeza, en este caso a la hora de intentar hacer un timeout que reciba una función con eventos.

Lo buscado: algo que permitiera llamar a un método de un determinado elemento con sus parámetros, seteándole un time out. Esto en cualquier browser que no sea IE se puede hacer fácil

setTimeout(function(arg){elem[func](arg)}, millisec, args);

donde arg es un array con los argumentos, elem es el elemento del DOM que contiene el método (func) y millisec es el timeOut en milisegundos.

Los problemas en IE son dos:

  1. No se le pueden pasar argumentos, por lo que hay que utilizar eval() o pasarle a setTimeOut() un string con el nombre de la función más sus argumentos, lo que nos lleva al punto 2.
  2. Si se pasa this como elemento (y en otros casos también) Internet Explorer pierde el contexto y resuelve this como cualquier cosa menos como el elemento que esperamos.

Bastante tiempo me pasé buscando una solución en José Google, hasta que dí con una en este post, en la cual ya había pensado y no me convencía resolverlo de esa forma. Al profundizar un poco más, pude ver en los comentarios de ese mismo post, que muchos utilizaban closures para solucionar este problema, aunque en esos casos no necesitaban pasar parámetros, por lo cual mi problema no se solucionaba completamente.

Para aquellos perezosos que no quieran seguir el link, pegaré y explicaré brevemente aquí un ejemplo de closures:

function sayHello(name) {
    var text = 'Hello ' + name;
    var sayAlert = function() { alert(text); }
    sayAlert();
}

En este ejemplo, al usar la palabra clave function para crear una función dentro de otra y utilizando una variable local de la función “padre” dentro de la “hija”, estamos creando una closure. Puede parecer simple de ver (hasta quizás lo hayamos usado muchas veces sin saberlo) pero es un tema bastante complejo, sobre el que deberíamos leer bastante. Esta no es la única forma de utilizar closures, pero es la que más me sirvió para mi ejemplo, ya que “guarda” el acceso a this, y me evita los problemas con IE.

La forma en que las closures solucionan mi problema es la siguiente:

var it = this;
this.delay(millisec, it, args, func);

En este código, llamo a una función del propio objeto, la cual se encarga de setear correctamente un time out para cualquier browser, pasándole como parámetros los milisegundos, argumentos, la función como string, y el mismo objeto. Este último parámetro es el más importante, ya que allí estoy usando una closure, al crear una variable local a la cual asigno el elemento, pasándola luego como parámetro de la función. El código del método delay sería más o menos así:

function delay(millisec, elem, args, func) {
    if (badBrowser()) {
        setTimeout(function() {ieTimeout()}, millisec);
    } else {
        setTimeout(function(arg){elem[func](arg)}, millisec, args);
    }
 
    function ieTimeout() {
        if (isArray(args)){
            for (var i=0; i < args.length; i++){
                if (isArray(args[i]))
                    args[i] = "[" + args[i] + "]";
            }
            args = "[" + args + "]";
        }
        eval("elem." + func + "(" + args + ")");
    }
}

La función badBrowser() devuelve true si el browser es IE =P, y false en otro caso. Como pueden ver, para el resto de los browsers utilizo la llamada a setTimeOut() con las variables directamente, y en el caso de IE llamo a una función “interna” que utiliza eval() para solucionar el problema de los argumentos que IE no permite pasar en setTimeOut().

Desventajas de esta solución:

  1. No se pueden pasar objetos como parámetros dentro del array porque al utilizarlos en la función ieTimeOut se convierten a su equivalente en String.
  2. También al pasar de array a String se pierden los “[]“, por lo cual en la función ieTimeOut me vi obligado a verificar que sean arrays (con otra función del framework) para agregarle los corchetes en caso de ser necesario.
  3. El eval(), por lo que estuve viendo, lentifica un poco la ejecución cuando es llamado repetidas veces, pero si no abusamos de él no causa mayores problemas.

Bien podría haber utilizado la función isTimeOut para todos los exploradores, pero teniendo en cuenta el punto 3 recién nombrado, me pareció más eficiente hacerlo de la forma “correcta” (además de que queda más linda =P).

Bueno, espero que esto haya servido de ayuda a alguien, cualquier duda, comenten! Y peguensé una vuelta por acá de vez en cuando, proximamente estaré publicando el Framework! =D

Saludos!

The Doctor

Tags: ,

This entry was posted on Monday, October 27th, 2008 at 1:27 pm and is filed under Desarrollo Web, Diseño Web, JavaScript, Ryff. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

Leave a Reply