Cabeza de Ratón: versus el Amor Letal

Upload con AJAX

Este artículo es la traducción de AJAX File upload Progress

El ejemplo de como usar la versión nueva está disponible en otro articulo

Actualización

Hay una nueva versión de este código que sigue la misma premisa pero usa HTML_AJAX en vez de JPspan.
Podes ver el nuevo demo y ver el código en websvn.
Observar también que el servidor no está configurado para aceptar archivos mayores de 8 Mb. Si es así, el script va a fallar.
También estoy buscando personas que me ayuden a mejorar el manejo de errores, si estas interesado en participar y hacer algunas actualizaciones de código nuevo, me lo hacen saber.

Sigue el artículo Original

Un par de días atrás, encontré un proyecto interesante de ruby on rails. Usa Ajax para actualizar una barra de progreso mientras sube un archivo. Este truco es un parche de rails para obtener el estado del upload e implementar dicho upload en un iframe mientras la pagina principal sigue activa.
Para implementar esto solo tenía que encontrar el parche de PHP que provee el estado de upload y luego implementarlo en mi pequeño iframe para uploads.
Encontré el código PHP, con un poco de trabajo, en Google: Barra de progreso de Upload
Primero necesitas instalar el parche y la extensión; las instrucciones que incluye son fáciles de seguir. El único problema que encontré es que hay que configurar: upload_progress_meter.store_method = “file” en el php.ini en antes de probar los scripts.
También tuve un problema con JPSpan, si estas teniendo conflictos de red el proceso de llamado de estado puede tomar mas de un segundo y vas a tener un alerta de error de llamada en progreso. Esto puede ser resuelto con la versión actual de JPSpan pero me gustaría ver alguna api agregada para mejorar los scripts. Los objetos con Proxy necesitan otro tipo de llamada en progreso para hacer simple el arreglo de errores.

Un punto a resaltar es que la extensión provee información solo de la transferencia total, de manera que solo se implementa una barra por formulario sin importar cuantos archivos sean. El código Javascript esta programado para implementar múltiples formularios en una misma página y que la transferencia se haga al mismo tiempo, pero no ha sido testeado.
Acá está el demo que estabas esperado, para la mayoría de las conexiones un archivo de 250k va a ser suficiente para apreciar algo mas que conectando y completo.
Además si alguien tiene el tiempo y las habilidades para revisar el parche .php y ver que necesitaría para estar integrado, por favor me lo hacen saber. No se nada del autor, por eso no sé por que no está integrado; pero parece medio loco tener un parche de 3k tan útil solamente disponible para aquellos que están dispuestos a parchear.

Detalle del código

El proceso básico sucede así:
Muestra una página con un formulario:
Esta página tiene un iframe oculto, un div de progreso oculto, y algo de código javascript extra.
Seleccionar el archivo a subir y enviar el formulario:
El formulario tiene como objetivo (target) el iframe oculto, por esto aunque el indicador de actividad del browser trabaje, la pagina principal no va a tener un nuevo contenido cuando el upload haya terminado.
El evento onclick del formulario dispara una función determinada:
La función localiza el div de progreso y lo muestra, también carga una función para actualizar el estado del progreso por segundo.
La función de actualización se dispara a cada segundo:
Esta función controla un contador para ver si hay algún div mas para actualizar; si el contador es cero detiene la función de actualización para que no se dispare otra vez.
La función crea un objeto proxy remoto para la clase php UploadProgressMeterStatus si aún no ha sido creado.
La función llama al método get_status en el objeto proxy con una lista de todos los divs de progreso y una de los UPLOAD_IDENTIFIER, de los que necesitamos los estados.
Sale de la función.
El método get-status en la clase de php es llamado.
El método llama a upload_progress_meter_get_info() para cada uno de los pasados en el identificador, a la información se le da formato de porcentaje y mensaje, los cuales son devueltos.
La función de retrollamada (callback) para get_status se llama cuando la clase PHP devuelve información.
La retrollamada (callback) actualiza el div de progreso.
Si este estuviera al 100%, decrementamos nuestro contador de divs de progreso y lo removemos de la lista de divs para ser actualizados.
El iframe puede tambien cargar una página una vez que el upload haya terminado, actualmente no hace nada.

Código de ejemplo

Este es realmente el único código PHP interesante en el proyecto, y no es tan interesante. Cuando tenés instalada la extensión de medidor de progreso, todo formulario que este haciendo un upload de un archivo y tenga una barra oculta que se llame UPLOAD_IDENTIFIER puede ser seguido. El identificador debe pasarse en la función upload_progress_meter_get_info, por lo cual tenés que hacer el seguimiento del mismo del lado de Javascript. Acá solo pasamos esos identificadores, hacemos un grupo de ellos en un array y devolvemos los resultados. Notar que el array devuelto no esta documentado en ningún lado por lo que este código y el código en el ejemplo de php provisto con la extensión es el mejor lugar para empezar, si querés.

/**
* Obtener el estado de todas las subidas que se pasen.
*/
function get_status($ids) {
$ret = array();
foreach($ids as $id => $upId) {
$ret[$id] =  new stdClass();

$tmp = upload_progress_meter_get_info($upId);

if (!is_array($tmp)) {
$ret[$id]->message = “Complete”;
$ret[$id]->percent = “100″;
break;
}

if ($tmp[‘bytes_total’] < 1)
$percent = 100;
}
else {
$percent = round($tmp[‘bytes_uploaded’] / $tmp[‘bytes_total’] * 100, 2);
}

if ($percent == 100) {
$ret[$id]->message = “Complete”;
}

$eta = sprintf(“%02d:%02d”, $tmp[‘est_sec’] / 60, $tmp[‘est_sec’] % 60 );
$speed = $this->_formatBytes($tmp[’speed_average’]);
$current = $this->_formatBytes($tmp[‘bytes_uploaded’]);
$total = $this->_formatBytes($tmp[‘bytes_total’]);

$ret[$id]->message = “$eta left (at $speed/sec) $current/$total($percent%)”;
$ret[$id]->percent = $percent;
}
return $ret;
}

Del lado de JavaScript hay un poco más de código, pero no es para nada complejo. Este código se usa para mostrar la barra de progreso y darle algunos valores iniciales. Las cosas mas importantes para resaltar son que hay agregado un método de actualización al div, este es un lindo truco por que permite la extensión en tiempo de ejecución de los objetos en DOM, y va a hacer actualizar las cosas agradable y fácilmente en otras funciones, también agregamos un método getFirstDivByClass al div, hago esto para no tener tantos div para seguir, las clases solo tienen que ser únicas dentro de la barra de proceso para que ande y eso es mucho mas fácil de lograr.

/**
* Muestra una barra de progreso y la establece a 0
*/
function UploadProgressMeter_EnableProgress(progress_id) {
var progress = document.getElementById(progress_id);
progress.style.display = ‘block’;
progress.percent = 0;
progress.message = "Connecting";

progress.update = function() {
this.getFirstDivByClass(‘bar’).style.width = this.percent+‘%’;
this.getFirstDivByClass(‘message’).innerHTML = this. message;
}

progress.getFirstDivByClass = function(className) {
var nodes = this.getElementsByTagName(‘div’);
for(var i = 0; i < nodes.length; i++) {
if (nodes[i].className == className) {
return nodes[i];
}
}
}
progress.update();
}

El código siguiente llama a un proxy remoto y crea una función de retrollamada (callback) para gestionar el resultado. Esta es un área donde se pueden hacer mejoras. Primero debería haber un control de si hay actualmente una llamada en progreso. Luego, sería inteligente llamar al servidor menos (especialmente en archivos grandes) y solo generar las estadísticas del índice de descarga actual. Esto agrega un poco de complejidad pero permitiría a la barra de progreso actualizar suavemente y permitiría que las llamadas al servidor bajen a una vez cada 5 o 10 segundos
Si no programas mucho en javascript no vale la pena el uso de for(var prop in result) y delete UploadProgressMeter_active[prop];
for(var prop in result) es como iteras en las propiedades de un objeto, esto te permite usarlas como arreglos asociativos (solo mira los métodos en los objetos ya que vas a iterar en ellos también).
delete UploadProgressMeter_active[prop] es el equivalente de unset($array[’key’]);

/**
* Actualizar las barras de progreso de todas las barras vigentes
*/
function UploadProgressMeter_Update() {
if (UploadProgressMeter_count == 0) {
clearInterval(UploadProgressMeter_intervalId);
UploadProgressMeter_intervalId = false;
return;
}

if (UploadProgressMeter_remote == false) {
var callback = {
get_status: function(result) {
for(var prop in result) {
if (prop != "toString") {
document.getElementById(prop).percent = result[prop].percent;
document.getElementById(prop).message = result[prop].message;
document.getElementById(prop).update();
if (document.getElementById(prop).percent == 100) {
UploadProgressMeter_count–;
delete UploadProgressMeter_active[prop];
}
}
}
}
}
UploadProgressMeter_remote = new uploadprogressmeterstatus(callback);
}
UploadProgressMeter_remote.get_status(UploadProgressMeter_active);
}

Code List
Actualizaciones

Te habras dado cuenta que el demo dejo de andar un par de veces. Esto está relacionado con dos cosas y son cosas que puede ser que quieras tener en cuenta si vas a usar el parche. Primero escribe archivos tmp y falla sileciosamente si no existen más (scripts que limpian los tmp). Segundo, es mi servidor php5 que tiene algunos usuarios que presionaron para que me actualice a php 5.1 beta y me olvide de reparchear. Reparchear no fue un gran problema aunque tube que mover donde la funcion fue declarada para obtener la extensión para compilar en gcc 4 (me actualice a fedora core 4 también)
De todos modos el demo está trabajando y debería seguir trabajando siempre y cuando me acuerde de reparchear con cada actualización.

Nueva versión

Este código ha sido actualizado para trabajar con HTML_AJAX y para manejar mejor las condiciones de error. No he hecho un lanzamiento oficial paro podes tomarlo del svn.
Ver en: http://svn.bluga.net/HTML_AJAX/UploadProgressMeter/trunk/
O usar websvn para obtener un tarball (archivo .tar): http://websvn.bluga.net/wsvn/HTML_AJAX/UploadProgressMeter/trunk/?rev=0&sc=0
También si estas interesado en ayudar con la medición de progreso de subida, me lo hacen saber.

  • Hola!! La verdad es que estoy muy interesada en todo esto de diseño web, pero no puedo ni siquiera ubicarme bien en este sitio.

    Sin embargo, quiero dejar un saludo por ser 18 de julio, al titular del blog. Que los cumplas muy feliz!!!

    Un abrazo. sara_eliana

  • Bueno, en realidad es 17 hoy, pero todo el mundo sabe que mañana es 18, y quiero ser previsora en esto de que el mensaje llegue a su destinatario con las previsiones del caso.

    Muchos cariños!!!

  • Bueno mami. Gracias por saludo. No se si es el lugar más apropiado, pero no me importa. Un beso grande. Damián.

You can follow any responses to this entry through the RSS 2.0 feed.

Trackbacks / Pingbacks