Capítulo 13 API Communication
Esta es la primera parte de lo que llamamos API Communication. Lo que extra oficialmente es conocido como API Communication es en realidad un grupo de APIs compuesto por XMLHttpRequest Level 2, Cross Document Messaging (API Web Messaging), y Web Sockets (API WebSocket). La primera de estas tres tecnologías de comunicación es una mejora del viejo objeto XMLHttpRequest usado extensamente hasta estos días para comunicarse con servidores y construir aplicaciones Ajax.
El nivel 2 de XMLHttpRequest incorpora nuevas características como comunicación con múltiples orígenes y eventos para controlar la evolución de las solicitudes. Estas mejoras simplifican códigos y ofrecen nuevas opciones, como interactuar con diferentes servidores desde la misma aplicación o trabajar con pequeñas trozos de datos en lugar de archivos enteros, por nombrar unas pocas.
El elemento más importante de esta API es, por supuesto, el objeto XMLHttpRequest. Un constructor fue especificado para crearlo:Devonn
XMLHttpRequest() Este constructor retorna un objeto XMLHttpRequest por medio del cual podemos comenzar una solicitud y escuchar eventos para controlar el proceso de comunicación.
El objeto creado por XMLHttpRequest() cuenta con importantes métodos para iniciar y controlar la solicitud:
open(método, url, asíncrono) Este método configura una solicitud pendiente. El atributo método declara el tipo de método HTTP usado para abrir la conexión (get o post). El atributo url declara la ubicación del código que va a procesar la solicitud. Y asíncrono es un valor booleano para declarar si la conexión será síncrona (false) o asíncrona (true). De ser necesario, el método también puede incluir valores especificando el nombre de usuario y la clave. send(datos) Este es el método que inicia la solicitud. Existen varias versiones de este método en un objeto XMLHttpRequest para procesar diferentes tipos de datos. El atributo datos puede ser omitido, declarado como un ArrayBuffer, un blob, un documento, una cadena de texto, o como FormData (ya estudiaremos este nuevo tipo de datos más adelante). abort() Este método cancela la solicitud.
Comencemos construyendo un ejemplo que obtiene el contenido de un archivo de texto en el servidor usando el método get. Vamos a necesitar un nuevo documento HTML con un botón para iniciar la solicitud:
<!DOCTYPE html>
<html lang=»es»>
<head>
<title>Ajax Level 2</title>
<link rel=»stylesheet» href=»ajax.css»>
<script src=»aj ax.js»></script>
</head>
<body>
<section id=»cajaformulario»>
<form name=»formulario»>
<p><input type=»button» name=»boton» id=»boton»
value=»Aceptar»></p>
</form>
</section>
<section id=»caj adatos»></section>
</body>
</html>
Listado 13-1. Plantilla para solicitudes Ajax.
Para hacer los códigos tan simples como sea posible mantuvimos la misma estructura HTML usada previamente y aplicamos solo los siguientes estilos por propósitos visuales:
#caj aformulario{
float: left;
padding: 20px;
border: 1px solid #999999;
}
#caj adatos{
float: left;
width: 500px;
margin-left: 20px;
padding: 20px;
border: 1px solid #999999;
}
Listado 13-2. Estilos para dar forma a las cajas en pantalla .
Hágalo usted mismo: Necesita crear un archivo HTML con la plantilla del Listado 13-1 y un archivo CSS llamado ajax.css con las reglas del Listado 13-2. Para poder probar estos ejemplos, deberá subir los archivos a su servidor, incluyendo el archivo Javascript y el que recibe la solicitud. Vamos a proveer más instrucciones en cada ejemplo.
El código para este primer ejemplo leerá un archivo en el servidor y mostrará su contenido en pantalla. Ningún dato es enviado al servidor; solo tenemos que hacer una solicitud get y mostrar la información obtenida en respuesta:
function iniciar(){
caj adatos=document.getElementByld(‘caj adatos’);
var boton=document.getElementById(‘boton’);
boton.addEventListener(‘click’, leer, false);
}
function leer(){
var url=»texto.txt»;
var solicitud=new XMLHttpRequest() ;
solicitud.addEventListener(‘load’ ,mostrar, false) ;
solicitud.open(«GET», url, true);
solicitud.send(null);
}
function mostrar(e){
caj adatos.innerHTML=e.target.responseText;
}
window.addEventListener(‘load’, iniciar, false);
Listado 13-3. Leyendo un archivo en el servidor.
En el código del Listado 13-3 incluimos nuestra típica función iniciar(). Esta función es llamada cuando el documento es cargado. Lo que hace en este caso es simplemente crear una referencia al elemento cajadatos y agrega una escucha para el evento click en el botón del formulario.
Cuando el botón «Aceptar» es presionado, la función leer() es ejecutada. Aquí podemos ver en acción todos los métodos estudiados previamente. Primero, la URL del archivo que será leído es declarada. No explicamos todavía cómo hacer solicitudes a diferentes servidores, por lo que este archivo deberá estar ubicado en el mismo dominio que el código Javascript (y en este ejemplo, también en el mismo directorio). En el siguiente paso, el objeto es creado por el constructor XMLHttpRequest () y asignado a la variable solicitud. Esta variable es usada luego para agregar una escucha para el evento load e iniciar la solicitud usando los métodos open() y send(). Debido a que ningún dato será enviado en esta solicitud, un valor null fue declarado en el método send(). En el método open(), en cambio, declaramos la solicitud como del tipo get, la URL del archivo a ser leído, y el tipo de operación (true para asíncrona).
Una operación asíncrona significa que el navegador continuará procesando el resto del código mientras espera que el archivo termine de ser descargado desde el servidor. El final de la operación será informado a través del método load. Cuando este evento es disparado, la función mostrar() es llamada. Esta función reemplaza el contenido del elemento caj adatos por el valor de la propiedad responseText, y el proceso finaliza.
Hágalo usted mismo: Para probar este ejemplo, cree un archivo de texto llamado texto.txt y agregue algún texto al mismo. Suba este archivo y el resto de los archivos creados con los códigos 13-1, 13-2 y 13-3 a su servidor y abra el documento HTML en su navegador. Luego de hacer clic sobre el botón «Aceptar», el contenido del archivo de texto es mostrado en pantalla.
Existen tres tipos diferentes de propiedades response que podemos usar para obtener la información retornada por la solicitud:
response Esta es una propiedad de propósito general. Retorna la respuesta de la solicitud de acuerdo al valor del atributo responseType. responseText Esta propiedad retorna la respuesta a la solicitud en formato texto. responseXML Esta propiedad retorna la respuesta a la solicitud como si fuera un documento XML.
Eventos
Además de load, la especificación incluye otros eventos para el objeto XMLHttpRequest:
loadstart Este evento es disparado cuando la solicitud comienza.
progress Este evento es disparado periódicamente mientras se envían o descargan datos.
abort Este evento es disparado cuando la solicitud es abortada.
error Este evento es disparado cuando un error ocurre durante el procesamiento de la solicitud.
load Este evento es disparado cuando la solicitud ha sido completada. timeout Si un valor para timeout ha sido especificado, este evento será disparado cuando la solicitud no pueda ser completada en el período de tiempo determinado. loadend Este evento es disparado cuando la solicitud ha sido completada (sin considerar si el proceso fue exitoso o no).
Quizás el evento más atractivo de todos sea progress. Este evento es disparado aproximadamente cada 50 milisegundos para informar acerca del estado de la solicitud.
Gracias a este evento podemos informar al usuario sobre cada paso del proceso y crear aplicaciones de comunicación profesionales.
function iniciar(){
caj adatos=document.getElementByld(‘caj adatos’);
var boton=document.getElementById(‘boton’); boton.addEventListener(‘click’, leer, false);
}
function leer(){
var url=»trailer.ogg»;
var solicitud=new XMLHttpRequest() ;
solicitud.addEventListener(‘loadstart’,comenzar,false); solicitud.addEventListener(‘progress’,estado,false); solicitud.addEventListener(‘load’,mostrar,false); solicitud.open(«GET», url, true); solicitud.send(null);
}
function comenzar(){
cajadatos.innerHTML='<progress value=»0″ max=»100″>0%</progress>’;
}
function estado(e){
if(e.lengthComputable){
var por=parseInt(e.loaded/e.total*100); var barraprogreso=cajadatos.querySelector(«progress»); barraprogreso.value=por; barraprogreso.innerHTML=por+’%’;
}
}
function mostrar(e){
caj adatos.innerHTML=’Terminado’;
}
window.addEventListener(‘load’, iniciar, false);
Listado 13-4. Informando el progreso de la solicitud.
En el Listado 13-4, el código usa tres eventos, loadstart, progress y load, para controlar la solicitud. El evento loadstart llama a la función comenzar() y la barra de progreso es mostrada en la pantalla por primera vez. Mientras el archivo es descargado, el evento progress ejecutará la función estado() constantemente. Esta función informa sobre el progreso de la operación a través del elemento <progress> creado anteriormente y el valor de las propiedades ofrecidas por el evento.
Finalmente, cuando el archivo es completamente descargado, el evento load es disparado y la función mostrar () imprime el texto «Terminado» en la pantalla.
El evento progress es declarado por la especificación en la interface ProgressEvent. Esta interface es común a cada API e incluye tres valiosas propiedades para retornar información sobre el proceso que es monitoreado por el evento:
lengthComputable Esta propiedad retorna true (verdadero) cuando el progreso puede ser calculado o false (falso) en caso contrario. Lo usamos en nuestro ejemplo para estar seguros de que los valores de las propiedades restantes son reales y válidos. loaded Esta propiedad retorna el total de bytes ya subidos o descargados. total Este propiedad retorna el tamaño total de los datos que están siendo subidos o descargados.
Hasta el momento hemos leído información desde el servidor, pero no hemos enviado ningún dato o incluso usado otro método HTTP además de get. En el siguiente ejemplo vamos a trabajar con el método post y un nuevo objeto que nos permite enviar información usando formularios virtuales.
En el ejemplo anterior no mencionamos cómo enviar datos con el método get porque, como siempre, es tan simple como agregar los valores a la URL. Solo tenemos que crear una ruta para la variable url como textfile.txt?val1=1&val2=2 y los valores especificados serán enviados junto con la consulta. Los atributos val1 y val2 de este ejemplo serán leídos como variables get del lado del servidor. Por supuesto, un archivo de texto no puede procesar esta información, por lo que normalmente deberemos recibir los datos usando un archivo programado en PHP, o en cualquier otro lenguaje de procesamiento en el servidor. Las solicitudes post, por otro lado, no son tan simples.
Como ya seguramente conoce, una solicitud post incluye toda la información enviada por un método get pero también el cuerpo del mensaje. El cuerpo del mensaje representa cualquier información de cualquier tipo y tamaño a ser enviada. Un formulario HTML es normalmente la mejor manera de proveer esta información, pero para aplicaciones dinámicas esta no es probablemente la mejor opción o la más apropiada. Para resolver este problema, la API incluye la interface FormData. Esta interface sencilla tiene solo un constructor y un método con el que obtener y trabajar sobre objetos FormData.
FormData() Este constructor retorna una objeto FormData usado luego por el método send() para enviar información.
append(nombre, valor) Este método agrega datos al objeto FormData. Toma un par clave/valor como atributos. El atributo valor puede ser una cadena de texto o un blob. Los datos retornados representan un campo de formulario.
function iniciar(){
caj adatos=document.getElementByld(‘caj adatos’);
var boton=document.getElementById(‘boton’); boton.addEventListener(‘click’, enviar, false);
}
function enviar(){
var datos=new FormData(); datos.append(‘nombre’,’Juan’); datos.append(‘apellido’,’Perez’);
var url=»procesar.php»;
var solicitud=new XMLHttpRequest() ;
solicitud.addEventListener(‘load’, mostrar, false); solicitud.open(«POST», url, true); solicitud.send(datos);
}
function mostrar(e){
caj adatos.innerHTML=e.target.responseText;
}
window.addEventListener(‘load’, iniciar, false);
Listado 13-5. Enviando un formulario virtual.
Cuando la información es enviada al servidor, es con el propósito de procesarla y producir un resultado. Normalmente este resultado es almacenado en el servidor y algunos datos son retornados como respuesta al usuario. En el ejemplo del Listado 13-5, los datos son enviados al archivo procesar.php y la respuesta de este código es mostrada en pantalla.
Para probar este ejemplo, el archivo procesar.php deberá imprimir los valores recibidos con un código similar al siguiente:
<?PHP
print(‘Su nombre es: ‘.$_POST[‘nombre’].'<br>’);
print(‘Su apellido es: ‘.$_POST[‘apellido’]);
?>
Listado 13-6. Respuesta simple a una solicitud post (procesar.php).
Veamos en primer lugar cómo la información fue preparada para ser enviada. En la función send() del Listado 13-5, el constructor FormData() es invocado y el objeto FormData retornado es almacenado en la variable datos. Dos pares clave/valor son agregados luego a este objeto con los nombres nombre y apellido usando el método append (). Estos valores representarán campos de formulario.
La inicialización de la solicitud es exactamente la misma que en códigos previos, excepto que esta vez el primer atributo del método open() es post en lugar de get, y el atributo del método send () es el objeto datos que acabamos de construir y no un valor nulo (null), como usamos anteriormente.
Cuando el botón «Aceptar» es presionado, la función send() es llamada y el formulario creado dentro del objeto FormData es enviado al servidor. El archivo procesar. php recibe estos datos (nombre y apellido) y retorna un texto al navegador incluyendo esta información. La función mostrar () es ejecutada cuando el proceso es finalizado. La información recibida es mostrada en pantalla desde esta función a través de la propiedad responseText.
Hágalo usted mismo: Este ejemplo requiere que varios archivos sean subidos al servidor. Vamos a utilizar el mismo documento HTML y estilos CSS de los Listados 13-1 y 13-2. El código Javascript en el Listado 13-5 reemplaza al anterior. También debe crear un nuevo archivo llamado procesar.php con el código del Listado 13-6. Suba todos estos archivos al servidor y abra el documento HTML en su navegador. Haciendo clic en el botón «Aceptar», debería ver en pantalla el texto retornado por procesar.php.
Hasta ahora hemos trabajado con códigos y archivos de datos ubicados en el mismo directorio y en el mismo dominio, pero XMLHttpRequest Level 2 nos deja hacer solicitudes a diferentes orígenes, lo que significa que podremos interactuar con diferentes servidores desde la misma aplicación.
El acceso de un origen a otro debe ser autorizado en el servidor. La autorización se realiza declarando los orígenes que tienen permiso para acceder a la aplicación. Esto es hecho en la cabecera enviada por el servidor que aloja el archivo que procesa la solicitud.
Por ejemplo, si nuestra aplicación está ubicada en www.dominio1.com y desde ella accedemos al archivo procesar.php ubicado en www.dominio2.com, el segundo servidor debe ser configurado para declarar al origen www.dominio1.com como un origen válido para una solicitud XMLHttpRequest.
Podemos especificar esta configuración desde los archivos de configuración del servidor, o declararlo en la cabecera desde el código. En el segundo caso, la solución para nuestro ejemplo sería tan simple como agregar la cabecera Access-Control-Allow- Origin al código del archivo procesar. php:
<?PHP
header(‘Access-Control-Allow-Origin: *’);
print(‘Su nombre es: ‘.$_POST[‘nombre’].'<br>’);
print(‘Su apellido es: ‘.$_POST[‘apellido’]);
?>
Listado 13-7. Autorizando solicitudes de orígenes múltiples.
El valor * para la cabecera Access-Control-Allow-Origin representa orígenes múltiples. El código del Listado 13-7 podrá ser accedido desde cualquier origen a menos que el valor * sea reemplazado por un origen específico (por ejemplo, http://www.dominio1. com, lo que solo autorizará a aplicaciones desde el dominio www.dominio1.com a acceder al archivo).
Subir archivos a un servidor es una tarea que tarde o temprano todo desarrollador debe enfrentar. Es una característica requerida por casi toda aplicación web estos días, pero no contemplada por navegadores hasta el momento. Esta API se hace cargo de la situación incorporando un nuevo atributo que retorna un objeto XMLHttpRequestUpload. Utilizando este objeto podemos acceder a todos los métodos, propiedades y eventos de un objeto XMLHttpRequest pero también controlar el proceso de subida.
upload Este atributo retorna un objeto XMLHttpRequestUpload. El atributo debe ser llamado desde un objeto XMLHttpRequest ya existente.
Para trabajar con este atributo vamos a necesitar una nueva plantilla con un elemento <input> desde el que seleccionaremos el archivo a ser subido:
<!DOCTYPE html>
<html lang=»es»>
<head>
<title>Ajax Level 2</title>
<link rel=»stylesheet» href=»ajax.css»> <script src=»aj ax.js»></script>
</head>
<body>
<section id=»cajaformulario»>
<form name=»formulario»>
<p>Archivo a Subir:<br><input type=»file» name=»archivos»
id=»archivos»></p>
</form>
</section>
<section id=»caj adatos»></section>
</body>
</html>
Listado 13-8. Plantilla para subir archivos.
Para subir un archivo tenemos que usar una referencia al archivo y enviarla como un campo de formulario. El objeto FormData estudiado en el ejemplo anterior es capaz de manejar esta clase de datos. El navegador detecta automáticamente la clase de información agregada al objeto FormData y crea las cabeceras apropiadas para iniciar la solicitud. El resto del proceso es exactamente el mismo estudiado anteriormente.
function iniciar(){
caj adatos=document.getElementById(‘caj adatos’);
var archivos=document.getElementById(‘archivos’); archivos.addEventListener(‘change’, subir, false);
}
function subir(e){
var archivos=e.target.files; var archivo=archivos[0];
var datos=new FormData(); datos.append(‘archivo’,archivo);
var url=»procesar.php»;
var solicitud=new XMLHttpRequest();
var xmlupload=solicitud.upload;
xmlupload.addEventListener(‘loadstart’,comenzar,false); xmlupload.addEventListener(‘progress’,estado,false); xmlupload.addEventListener(‘load’,mostrar,false); solicitud.open(«POST», url, true); solicitud.send(datos);
}
function comenzar(){
cajadatos.innerHTML='<progress value=»0″ max=»100″>0%</progress>’;
}
function estado(e){
if(e.lengthComputable){
var por=parseInt(e.loaded/e.total*100);
var barraprogreso=cajadatos.querySelector(«progress») ;
barraprogreso.value=por; barraprogreso.innerHTML=por+’%’;
}
}
function mostrar(e){
caj adatos.innerHTML=’Terminado’;
}
window.addEventListener(‘load’, iniciar, false);
Listado 13-9. Subiendo un archivo con FormData.
La principal función del Listado 13-9 es subir(). Esta función es llamada cuando el usuario selecciona un nuevo archivo desde el elemento <input> (y el evento change es disparado). El archivo seleccionado es recibido y almacenado en la variable archivo, exactamente del mismo modo que lo hicimos anteriormente para aplicar la API File en el Capítulo 12 y también para la API Drag and Drop en el Capítulo 8. Los métodos usados en cada ocasión retornan un objeto File.
Una vez que tenemos la referencia al archivo, el objeto FormData es creado y el archivo es agregado a este objeto por medio del método append(). Para enviar este formulario, iniciamos una solicitud post. Primero, un objeto XMLHttpRequest común es asignado a la variable de la solicitud. Más adelante, usando el atributo upload, un objeto XMLHttpRequestUpload es creado y almacenado en la variable xmlupload. Usando esta variable agregamos escuchas para todos los eventos disparados por el proceso de subida y finalmente la solicitud es enviada send(datos).
El resto del código hace lo mismo que en el ejemplo del Listado 13-4; en otras palabras, una barra de progreso es mostrada en la pantalla cuando el proceso de subida comienza y luego es actualizada de acuerdo al progreso del mismo.
Hágalo usted mismo: En este ejemplo indicamos que el archivo procesar.php se encargará de procesar los archivos enviados al servidor, pero no hacemos nada al respecto. Para probar el código anterior, puede utilizar un archivo procesar.php vacío.
Subir un archivo a la vez probablemente no sea lo que la mayoría de los desarrolladores tengan en mente. Así como tampoco lo es utilizar el elemento <input> para seleccionar los archivos a subir. En general, todo programador busca que sus aplicaciones sean lo más intuitivas posible, y qué mejor manera de lograrlo que combinando técnicas y métodos a los que los usuarios ya están familiarizados. Aprovechando API Drag and Drop, vamos a crear una aplicación que se asemeja a lo que normalmente usamos en la vida real. Los archivos podrán ser subidos al servidor simplemente arrastrándolos desde el Explorador de Archivos hasta un área en la página web.
Creemos primero un documento HTML con la caja donde soltar los archivos:
<!DOCTYPE html>
<html lang=»es»>
<head>
<title>Ajax Level 2</title>
<link rel=»stylesheet» href=»ajax.css»>
<script src=»aj ax.js»></script>
</head>
<body>
<section id=»cajadatos»>
<p>Suelte los archivos aquí</p>
</section>
</body>
</html>
Listado 13-10. Área para soltar los archivos a subir.
El código Javascript para este ejemplo es probablemente el más complejo de los que hemos visto hasta el momento a lo largo del libro. No solo combina varias APIs sino también varias funciones anónimas para mantener todo organizado y dentro del mismo entorno (dentro de la misma función). Este código debe tomar los archivos soltados dentro del elemento caj adatos, listarlos en la pantalla, preparar el formulario virtual con esta información, hacer una solicitud para subir cada archivo al servidor y actualizar las barras de progreso de cada uno mientras son subidos.
function iniciar(){
caj adatos=document.getElementById(‘caj adatos’);
cajadatos.addEventListener(‘dragenter’, function(e){
e.preventDefault(); }, false); cajadatos.addEventListener(‘dragover’, function(e){
e.preventDefault(); }, false); cajadatos.addEventListener(‘drop’, soltado, false);
}
function soltado(e){ e.preventDefault() ; var archivos=e.dataTransfer.files; if(archivos.length){ var lista=»;
for(var f=0;f<archivos.length;f++){ var archivo=archivos[f];
lista+='<blockquote>Archivo: ‘+archivo.name;
lista+='<br><span><progress value=»0″ max=»100″>0%
</progress></span>’;
lista+='</blockquote>’;
}
caj adatos.innerHTML=lista;
var cuenta=0;
var subir=function(){
var archivo=archivos[cuenta];
var datos=new FormData();
datos.append(‘archivo’,archivo);
var url=»procesar.php»;
var solicitud=new XMLHttpRequest();
var xmlupload=solicitud.upload;
xmlupload.addEventListener(‘progress’,function(e){ if(e.lengthComputable){ var hijo=cuenta+1;
var por=parseInt(e.loaded/e.total*100) ;
var barraprogreso=caj adatos.querySelector(«block
quote:nth-child(«+hijo+») > span > progress»); barraprogreso.value=por; barraprogreso.innerHTML=por+’%’;
}
},false);
xmlupload.addEventListener(‘load’,function(){ var hijo=cuenta+1;
var elemento=caj adatos.querySelector(«blockquote:nth-
child(«+hijo+») > span»); elemento.innerHTML=’Terminado!’;
cuenta++;
if(cuenta<archivos.length){ subir();
}
},false);
solicitud.open(«POST» , url, true); solicitud.send(datos);
}
subir();
}
}
window.addEventListener(‘load’, iniciar, false);
Listado 13-11. Subiendo archivos uno por uno.
Bien, no es un código cómodo para analizar, pero será fácil de entender si lo estudiamos paso a paso. Como siempre, todo comienza con la llamada a la función iniciar () cuando el documento es completamente cargado. Esta función crea una referencia al elemento cajadatos que será la caja donde soltar los archivos, y agrega escuchas para tres eventos que controlan la operación de arrastrar y soltar. Para conocer cómo se procesa exactamente esta operación, lea nuevamente el Capítulo 8. Básicamente, el evento dragenter es disparado cuando los archivos que son arrastrados ingresan en el área del elemento cajadatos, el evento dragover es disparado periódicamente cuando los archivos arrastrados están sobre este elemento, y el evento drop es disparado cuando los archivos son finalmente soltados dentro de la caja en la pantalla. No debemos hacer nada para responder a los eventos dragenter y dragover en este ejemplo, por lo que los mismos son cancelados para prevenir el comportamiento por defecto del navegador. El único evento al que responderemos es drop. La escucha para este evento llama a la función soltado () cada vez que algo es soltado dentro de caj adatos.
La primera línea de la función soltado() también usa el método preventDefault () para poder hacer con los archivos lo que nosotros queremos y no lo que el navegador haría por defecto. Ahora que tenemos absoluto control de la situación, es tiempo de procesar los archivos soltados. Primero, necesitamos obtener la lista de archivos desde el elemento dataTransfer. El valor retornado es un array que almacenamos en la variable archivos. Para estar seguros de que lo que fue soltado son archivos y no otra clase de elementos, controlamos el valor de la propiedad length con el condicional if(archivos.length). Si este valor es diferente a 0 o null significa que uno o más archivos han sido soltados dentro de la caja y podemos continuar con el proceso.
Es hora de procesar los archivos recibidos. Con un bucle for navegamos a través del array archivos y creamos una lista de elementos <blockquote> conteniendo cada uno el nombre del archivo y una barra de progreso encerrada en etiquetas <span>. Una vez que la construcción de la lista es finalizada, el resultado es mostrado en pantalla como el contenido de caj adatos.
A simple vista parece que la función soltado () hace todo el trabajo, pero dentro de esta función creamos otra llamada subir() que se hace cargo del proceso de subir los archivos uno por uno al servidor. Por lo tanto, luego de mostrar todos los archivos en pantalla la siguiente tarea es crear esta función y llamarla por cada archivo en la lista.
La función subir() fue creada usando una función anónima. Dentro de esta función, primero seleccionamos un archivo desde el array usando la variable cuenta como índice. Esta variable fue previamente inicializada a 0, por lo que la primera vez que la función subir () es llamada, el primer archivo de la lista es seleccionado y subido.
Para subir cada archivo usamos el mismo método que en anteriores ejemplos. Una referencia al archivo es almacenada en la variable archivo, un objeto FormData es creado usando el constructor FormData() y el archivo es agregado al objeto con el método append().
En esta oportunidad, solo escuchamos a dos eventos: progress y load. Cada vez que el evento progress es disparado, una función anónima es llamada para actualizar el estado de la barra de progreso del archivo que está siendo subido. Para identificar el elemento <progress> correspondiente a ese archivo, usamos el método querySelector() con la pseudo clase :nth-child(). El índice de la pseudo clase es calculado usando el valor de la variable cuenta. Esta variable contiene el número del índice del array archivos, pero este índice comienza en 0 mientras que el índice para la lista de hijos accedidos por :nth- child() se inicia en el valor 1. Para obtener el valor del índice correspondiente y referenciar el elemento <progress> correcto, agregamos 1 al valor de cuenta, almacenamos el resultado en la variable hijo y usamos esta variable como índice.
Cuando el proceso anterior es terminado, tenemos que informar sobre la situación y avanzar hacia el siguiente archivo en el array archivos. Para este propósito, en la función anónima ejecutada cuando el evento load es disparado, incrementamos el valor de cuenta en una unidad, reemplazamos el elemento <progress> correspondiente al archivo subido por el texto «Terminado!», y llamamos a la función subir() nuevamente (siempre y cuando queden aún archivos por procesar).
Volvamos un poco a ver a grandes rasgos cómo el proceso es desarrollado. Si sigue el código del Listado 13-11, verá que, luego de declarar la función subir (), ésta es llamada por primera vez al final de la función soltado(). Debido a que la variable cuenta es inicializada a 0, el primer archivo contenido en el array archivos será procesado en primer lugar. Más adelante, cuando este archivo es subido por completo, el evento load es disparado y la función anónima llamada para responder al mismo incrementará el valor de cuenta una unidad y ejecutará la función subir() nuevamente para procesar el archivo siguiente. Al final, todos los archivos arrastrados y soldados dentro de la caja en pantalla habrán sido subidos al servidor, uno por uno.