Programando interfaces con JavaScript me topé con un problema al que no encontré una solución concreta.
Cuando tenemos una página dinámica que carga scripts JS usando AJAX a medida que los va necesitando, podemos caer en el problema de cargar el mismo script más de una vez. Esto se debe a que de antemano no sabemos si el script se cargó o no y lo necesitamos para cumplir alguna función.
Un ejemplo rápido de ver es el caso de asignar un evento a un componente. Supongamos que tenemos una página con el siguiente componente:
<DIV ID="contenedor">contenido</DIV>
y tenemos una sección que cambia según el accionar del usuario, pero carga un script jQuery de la siguiente forma:
$.getScript('/js/bind.js');
cuyo contenido es el siguiente:
$('#contenedor').click(function(event){
$("#contenedor").append("hola");
});
Si llamamos la misma sección más de una vez, lo que sucederá es que el evento click se enlaza más de una vez al componente "contenedor", haciendo que la palabra "hola" se agregue varias veces, en lugar de una sola vez como se esperaría.
Van entendiendo por dónde va el problema? De alguna forma, la sección que carga el script debería saber que el script ya se cargó y no debería cargarlo de nuevo. El tema es cómo saber esto...
En internet encontré algunas alternativas (sobre todo en StackOverflow) que explican cómo escapar este problema, pero al parecer ni JavaScript, ni jQuery implementan una solución directa, el programador debe encargarse de diseñar una función a tal fin.
Otro problema con el que me topé relacionado a esto es cómo saber en qué momento se terminaron de cargar varios scripts. Si usas jQuery, ellos recomiendan usar la función $(document).ready() para esperar a que la página se cargue. La realidad es que cuando se cargan scripts con $.getScript, el evento ready no espera a que estos scripts se carguen. No obstante $.getScript permite definir una función como parámetro que será llamada cuando el script se termine de cargar... pero y si queremos esperar a que un conjunto de scripts se carguen?
Para matar dos pájaros de un tiro (los problemas citados), diseñé una función jQuery denominada load_script, la cual decidí compartir con el mundo porque ninguna de las soluciones que encontré me pareció completa. Esta función está basada en distintas propuestas de distintos usuarios de StackOverflow.
La función utiliza dos variables globales: loaded_scripts y loading_scripts. loaded_scripts mantiene la lista de scripts que ya se cargaron, así no se los vuelve a cargar nuevamente, mientras que loading_scripts mantiene la lista de scripts que se están cargando actualmente, para saber en qué momento se terminó de cargar todo.
Los parámetros de la función son: script_list y callback. script_list es un arreglo con la lista de scripts que se deben cargar y por los cuales debe esperarse a que carguen, mientras que callback es una función definida por el programador que se llamará cuando todos los scripts se terminen de cargar.
/**
* Load the given JavaScript scripts and call the callback function when all of them are fully loaded.
* Each script is loaded only once, meanning that if this function is called two times with the same script name
* it will be loaded the first time. This avoids problems like redeclared functions or variables, event binded more than
* once, and every other error produced by a script loaded multiple times.
* The function uses two global array variables:
* loaded_scripts: scripts that has been already loaded. Used to know which scripts should not be loaded again.
* loaded_scripts: scripts that are in loading process. Used to know when the loading process is done.
*
* @param script_list array containing the scripts links to load. Ex: ['/js/load_this.js', '/js/binder.js']
* @param callback the function to call when loading of the scripts is done.
*/
var loaded_scripts = Array();
var loading_scripts = Array();
jQuery.fn.load_scripts = function(script_list, callback)
{
var script;
//check for already loaded scripts and so they're not loaded again
for(s in script_list)
{
if(loaded_scripts.indexOf(script_list[s]) == -1)
loading_scripts.push(script_list[s]);
}
//if all the requested scripts are already loaded, callback and return
if(loading_scripts.length == 0)
{
callback();
return;
}
for(s in loading_scripts)
{
script = loading_scripts[s];
$.getScript(script, function() {
//when script is loaded, remove it from the loading scripts array.
//if it's the last script on the array, it means we're done loading, so call the callback function.
loading_scripts.splice(loading_scripts.indexOf(script), 1);
loaded_scripts.push(script);
if((loading_scripts.length == 0) && (callback !== undefined))
callback();
});
}
}
Un ejemplo de uso es el siguiente:
$(window).load_scripts(['/js/load_this.js', '/js/binder.js'], function() {
$('#contenedor').click(function(event){
$("#contenedor").append("hola");
});
});
Espero que la función les sirva, al menos como ejemplo, ya que por lo que vi es un problema recurrente en los foros. Si encuentran algún error en el código, me avisan en los comentarios ;)
0 comentarios:
Publicar un comentario