Wednesday 27 May 2009

Cargar javascript desde JSON o AJAX con eval() en FF y execScript() en IE

¿Estás desarrollando una aplicación web en la que usas JSON o AJAX para cargar dinámicamente datos? ¿Además necesitas cargar dinámicamente no solamente datos sino también código javascript para ser utilizado en la página? A continuación te expongo el modo de hacerlo para que funcione en FireFox 3.X y IE7 (casualmente, también funciona en IE6).

Voy a partir de la base de que utilizas jQuery para la consulta JSON o AJAX al servidor. Y me voy a centrar en la "carga" del código javascript. Pero obviamente, no importa la librería o código que uses para el JSON o AJAX. Hay muchísima literatura en internet sobre ese tema.

Ejemplo de carga de datos

Empezaré con un ejemplo de carga de datos tipo texto, que es lo más habitual. Y luego pondré un ejemplo de carga de código javascript.


function js_actualizar_top_webs(){
var url_json = 'json_top_webs.php?mes='+$('#select_mes').val();
$.getJSON(
url_json,
function(datos){
$('#div_top_webs').html(datos.table_webs);
}
);
}

Imaginemos que en nuestra web de ejemplo tenemos un control tipo lista desplegable (select) que permite al usuario escoger un mes del año, y queremos que nuestra página recargue un <div id="'div_top_webs'"></div> con la lista del top 10 de webs de ese mes. Es un ejemplo clásico de recarga de datos dinámica usando JSON o AJAX.

Para ello construimos una función llamada js_actualizar_top_webs, que llamaremos al cambiar el valor de ese select. Esta función tal como está definida en el código de ejemplo hace una llamada JSON mediante el objeto $ de jQuery y cuando los datos se hayan recibido por completo ejecutará el código en rojo, en dónde "datos" es un objeto de javascript (no un array!!) que contiene diferentes "propiedades" con los datos enviados. En este caso, suponemos que el archivo de PHP que ha sido llamado devuelve un string llamado "table_webs" que contiene el código HTML de un "<table>" con la lista de webs que queremos mostrar.

Para "obtener" el valor de ese string HTML simplemente llamamos a datos.table_webs y lo colocamos como "contenido" de "div_top_webs" mediante el método .html() de jQuery. ¿Sencillo, no?


Ejemplo de carga de código javascript


Bueno, vamos ahora a algo que "cuesta" un poquitín más. Bueno, de hecho a mí me ha hecho gastar casi 8 horas de cansada búsqueda por internet y de leer mucho y probar mucho, hasta llegar a lo que te voy a poner aquí! ;)

Abajo tienes el código anterior ligeramente cambiado: hemos añadido unas líneas de código más, con el fin de poder cargar y ejecutar un código de javascript elaborado en el lado servidor.



function js_actualizar_top_webs(){
var url_json = 'json_top_webs.php?mes='+$('#select_mes').val();
$.getJSON(
url_json,
function(datos){
$('#div_top_webs').html(datos.table_webs);
if (window.execScript) window.execScript(datos.script_leyenda);
else window.eval(datos.script_leyenda);
}
);
}

Puedes ver en rojo dos nuevas líneas que corresponden a una sentencia condicional, y esto es necesario para ejecutar un código u otro según estemos en FireFox o en Internet Explorer (como mínimo estos dos navegadores). Para ambos casos el código de javascript pasado es el mismo y lo recuperamos de nuevo mediante jQuery con la propiedad datos.script_leyenda (que habremos rellenado desde el PHP con el código javascript que necesitemos ejecutar en nuestra web).

La diferencia radica en que Internet Explorer ejecutará el código de javascript mediante el método window.execScript(), mientras que FireFox ejecutará el código al usar el método window.eval(), en ambos casos pasando como argumento el código javascript que queremos ejecutar.

Otro aspecto importante y que tal vez pase desapercibido a primera vista es que en ambos casos ejecutamos sendos métodos sobre el objeto window. Eso es fundamental (me llevó más de una hora leyendo descubrirlo), para que el código javascript a ejecutar pase a estar "disponible" desde otros "ámbitos" de la misma página. De esta forma, si por ejemplo, dentro de ese código cargado dinámicamente definimos una función llamada "js_mostrar_leyenda()", si queremos usarla en el resto de la página, deberemos llamarla así: window.js_mostrar_leyenda();

Estoy seguro de que a más de uno le va a ir de perlas estas 3 líneas de código! al menos a mí me va a dar mucha potencia de programación con JSON y javascript. Y sinceramente, cuesta encontrar información "actualizada" en internet. Ya sabes que este tipo de información técnica (especialmente cross-browser) se desfasa con facilidad, con la salida de nuevas versiones de navegadores continuamente!! Así que lo que es válido hoy deja de serlo al cabo de 2 años :(

Te invito a que comentes cualquier sugerencia que tengas al respecto, eh! seguro que a todos nos interesa (temas de compatibilidad, por ejemplo). Si probando el código ves que te funciona en otros navegadores, por favor, coméntalo brevemente ;)


Actualización 2012-mar-06

Bajo petición de un comentarista añado a continuación un ejemplo de cómo debería ser el código PHP del archivo

json_top_webs.php 

que uso en los ejemplos de arriba.

1  <?php
2 
3       $ret
=array(
4                 
'table_webs'=>'<p>table</p>',
5                 
'script_leyenda'=>"alert('Hi world');"
6                 
);
7                
8       echo 
json_encode($ret);
9 
10  
?>

No necesita mucha explicación, pero por los que empiezan con el PHP:
  • definimos un array con dos elementos que llevan el nombre que luego usaremos en el javascript (como "datos.table_webs").
  • fijaros en el valor de cada uno de ellos. El primero es código HTML tal cuál, y el segundo es CUALQUIER instrucción o instrucciones de javascript, desde un simple alert() hasta definir funciones si es necesario.
  • por último hacemos un echo del array pasándolo primero por la función nativa de PHP json_encode() que lo que hace es reescribir los datos en formato JSON (ya sabéis: con llaves, comillas dobles, y comas, escapando los valores que haga falta). Cuidado porqué esta función creo que no estaba en algunas versiones de PHP 4.x ! pero en fin, quiero pensar que hace años que todo el mundo actualizó ya a PHP 5.x

Un saludo!!!
SERGI

Thursday 21 May 2009

FireFox, solución a "Ha escogido abrir..."

Este artículo es un poco técnico, pero me veo obligado a publicarlo para compartir la solución a un problema que creo que es más común de lo que parece y sobre el que he encontrado muy poco escrito. Pienso que los desarrolladores de aplicaciones web que lo lean me lo agradecerán. Tal vez no te haya ocurrido nunca, pero ya llegará el día, jejejeje...

Signos del problema

- Cuando se intenta acceder a ciertas páginas de la aplicación, el navegador tarda en responder como casi un minuto y luego -al menos en FireFox- aparece una ventana en la que el mensaje dice "Ha escogido abrir este archivo..." y te da la opción de guardar el "supuesto" archivo PHP en lugar de mostrar el contenido de la web. Si pruebas a visitar la misma página desde IE la página quedará completamente en blanco y no mostrará nada.

- Es posible que algunas páginas de la web si vayan, porque sean HTML, pero también algunas de PHP también pueden verse mientras otras no. Después de leer el artículo entero entenderás porqué.

- El problema te ocurre tanto si visitas las web desde tu PC o desde cualquier otro. Aunque ten cuidado con las copias guardadas en caché!!! te pueden jugar "bromas pesadas": hacerte creer que una página se ve bien cuando realmente no es así, o al revés! Para estar seguro siempre de que el navegador te muestra la página devuelta por el servidor te aconsejo refrescar la página con CTRL+F5.

Causa del problema

- Tu aplicación web está teniendo algún "problema" en algún punto de sus "cálculos": en algún lugar del código se entra en una especie de "bucle infinito" que deja a la aplicación "pensando indefinidamente". Por esta causa el servidor da una respuesta "incoherente" al navegador, o digamos que el navegador no sabe interpretar la respuesta (¿curioso, verdad?).

- Por ejemplo, en mi caso se trataba de una clase que yo había programado para acceder a un archivo histórico, que cuando accede bloquea/desbloquea un archivo como señal de alerta para otros "hilos" de la aplicación, de que no pueden acceder en ese momento a la aplicación. La cuestión es que este sistema me funciona sin problemas (hasta ahora). Pero la empresa de hosting hizo una especie de restauración de backup en los servidores que debieron alterar los permisos de escritura sobre el directorio en el que está ubicado el archivo. En conclusión: mi aplicación se quedaba encallada en ese punto: intentando acceder a ese archivo permanentemente "bloqueado". Una vez que descubrí este fenómeno, la solución fue fácil: por FTP cambié los atributos del directorio para poderse escribir en él!

- Sin embargo, mi caso es muy particular y entiendo que difícil de repetir por otros programadores. Pero no así el problema de fondo: dejar a la aplicación en "stand by" (o en bucle) hasta resolver algo irresoluble. Por ejemplo, entre los comentarios que he leído en otros foros, hay gente que le ha pasado exactamente lo mismo cuando su aplicación web intenta hacer un "fopen", que es una lectura de un archivo remoto con PHP. Lo que quiero decir es que si no te ha pasado todavía (como desarrollador web) es muy posible que te pueda pasar en breve. Sobretodo hoy en día en el que la programación con webservices y consultas remotas está tan a la orden del día ;)

- [Nota añadida 8-ago-2009] Hoy me ha ocurrido otra vez un bucle de esos que me ocasionó el mismo error! Aunque esta vez, sabiendo que se trataba de un bucle infinito he ido haciendo "debugeo" hasta encontrar donde se me "colapsaba" la aplicación ;) El bucle era del tipo "función recursiva MAL HECHA":

function mi_funcion(){
if (condicion)
return mi_funcion();
else
return 'a otra cosa mariposa';
}


Con lo cuál, en los casos en que "condición" es verdadero se llama de nuevo a la misma función, en un bucle infinito!! ¿Cuál fue esta vez mi error...? pues que en realidad cuando escribí el código quería llamar a una "mi_segunda_funcion()" en el caso de que "condicion" fuera verdadero... en fin, que me equivoqué con el nombre de la función!!! :((( es uno de esos errores del "copiar y pegar", del que no hace falta que diga mucho más, verdad?! jejeje...

Bueno, en fin, cualquier comentario al respecto será bienvenido, si te ha pasado lo mismo o algo parecido. No dudes en comentarlo aquí... igual que tú has llegado hasta aquí con ese problema, detrás tuyo vendrá más gente a quien le podrá ser útil! no olvides escribir tu solución cuando la halles! ;)

Un saludo!
SERGI
PD: está lloviendo!!! jajajaja... se agradece en este tórrido territorio (desierto de Sonora, México) en donde solo ayer estabamos a 43 grados (a finales de Mayo...).