API Communication

Ajax nivel 2
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:
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.
Obteniendo datos
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="ajax.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="cajadatos"></section>
</body>
</html>

 

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:
#cajaformulario{
float: left;
padding: 20px;
border: 1px solid #999999;
}
#cajadatos{
float: left;
width: 500px;
margin-left: 20px;
padding: 20px;
border: 1px solid #999999;
}

 

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(){
cajadatos=document.getElementById('cajadatos');
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){
cajadatos.innerHTML=e.target.responseText;
}
window.addEventListener('load', iniciar, false);

 

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 cajadatos 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.
IMPORTANTE: Cuando la respuesta es procesada usando innerHTML, los códigos
HTML y Javascript son procesados. Por razones de seguridad siempre es mejor
usar innerText en su lugar. Usted deberá tomar la decisión de utilizar uno u
otro de acuerdo a las características de su aplicación.
 

Propiedades response
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(){
cajadatos=document.getElementById('cajadatos');
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){
cajadatos.innerHTML='Terminado';
}
window.addEventListener('load', iniciar, false);

 

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.
IMPORTANTE: En nuestro ejemplo utilizamos innerHTML para agregar un nuevo
elemento <progress> al documento. Esta no es una práctica recomendada pero
es útil y conveniente por razones didácticas. Normalmente los elementos son
agregados al documento usando el método Javascript createElement() junto
con appendChild().
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.
IMPORTANTE: Dependiendo de su conexión a Internet, para ver cómo la barra de
progreso trabaja, es posible que deba usar archivos extensos. En el código del
Listado 13-4, declaramos la URL como el nombre del video usado en el Capítulo 5
para trabajar con la API de medios. Puede usar sus propios archivos o descargar
este video en: www.minkbooks.com/content/trailer.ogg.
 

Enviando datos
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(){
cajadatos=document.getElementById('cajadatos');
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){
cajadatos.innerHTML=e.target.responseText;
}
window.addEventListener('load', iniciar, false);

 

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.
 

Solicitudes de diferente origen
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']);
?>

 

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, https://www.dominio1.com,
lo que solo autorizará a aplicaciones desde el dominio www.dominio1.com a acceder al
archivo).
IMPORTANTE: El nuevo código PHP del Listado 13-7 agrega el valor solo a la
cabecera retornada por el archivo procesar.php. Para incluir este parámetro
en la cabecera de cada uno de los archivos retornados por el servidor,
necesitamos modificar los archivos de configuración del servidor HTTP. Para
encontrar más información al respecto, visite los enlaces correspondientes a este
capítulo en nuestro sitio web o lea las instrucciones de su servidor HTTP.
 

Subiendo archivos
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="ajax.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="cajadatos"></section>
</body>
</html>

 

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(){
cajadatos=document.getElementById('cajadatos');
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){
cajadatos.innerHTML='Terminado';
}
window.addEventListener('load', iniciar, false);

 

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.
 

Aplicación de la vida real
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="ajax.js"></script>
</head>
<body>
<section id="cajadatos">
<p>Suelte los archivos aquí</p>
</section>
</body>
</html>

 

Á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 cajadatos, 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(){
cajadatos=document.getElementById('cajadatos');
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>';
}
cajadatos.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=cajadatos.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=cajadatos.querySelector("blockquote:nthchild("+
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);

 

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 cajadatos.
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 cajadatos.
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 :nthchild()
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.
 

Cross Document Messaging
Esta parte de lo que llamamos API Communication es conocida oficialmente como API
Web Messaging
. Cross Document Messaging es una técnica que permite a aplicaciones de
diferentes orígenes comunicarse entre sí. Aplicaciones funcionando en diferentes cuadros,
ventanas o pestañas (o incluso otras APIs) pueden comunicarse ahora aprovechando esta
tecnología. El procedimiento es simple: publicamos un mensaje desde un documento y lo
procesamos en el documento destino.
 

Constructor
Para publicar mensajes, la API provee el método postMessage():
postMessage(mensaje, destino) Este método es aplicado al contentWindow del objeto
Window que recibe el mensaje. El atributo mensaje es una cadena de texto
representando el mensaje a transmitir, y el atributo destino es el dominio del
documento destino (que puede ser una URL o un puerto, como veremos más adelante).
El destino puede ser declarado como un dominio específico, como cualquier documento
usando el símbolo *, o como el mismo origen del documento que envía el mensaje
usando el símbolo /. El método puede también incluir un array de puertos como tercer
atributo.
 

Evento message y propiedades
El método de comunicación es asíncrono. Para escuchar por mensajes enviados por un
documento en particular, la API ofrece el evento message, el cual incluye algunas
propiedades con información sobre la operación:
data Esta propiedad retorna el contenido del mensaje.
origin Esta propiedad retorna el origen del documento que envió el mensaje, generalmente
el dominio. Este valor puede ser usado luego para enviar un mensaje de regreso.
source Esta propiedad retorna un objeto usado para identificar a la fuente del mensaje.
Este valor puede ser usado para apuntar al documento que envía el mensaje, como
veremos más adelante.
 

Enviando mensajes
Para crear un ejemplo de esta API, tenemos que considerar lo siguiente: la comunicación
ocurre entre diferentes ventanas (ventanas, cuadros, pestañas u otras APIs), debido a esto
debemos generar documentos y códigos para cada extremo del proceso. Nuestro ejemplo
incluye una plantilla con un iframe (cuadro interno) y los códigos Javascript apropiados
para cada documento HTML. Comencemos por el documento principal:
<!DOCTYPE html>
<html lang="es">
<head>
<title>Cross Document Messaging</title>
<link rel="stylesheet" href="messaging.css">
<script src="messaging.js"></script>
</head>
<body>
<section id="cajaformulario">
<form name="formulario">
<p>Su nombre: <input type="text" name="nombre" id="nombre"
required></p>

<p><input type="button" name="boton" id="boton"
value="Enviar"></p>
</form>
</section>
<section id="cajadatos">
<iframe id="iframe" src="iframe.html" width="500"
height="350"></iframe>
</section>
</body>
</html>

 

Plantilla para comunicación entre documentos.
Como podemos ver, al igual que en plantillas previas, incluimos dos elementos
<section>, pero esta vez cajadatos contiene un <iframe> que cargará el archivo
iframe.html. Vamos a volver a esto en un momento. Antes agreguemos algunos estilos
a esta estructura:
#cajaformulario{
float: left;
padding: 20px;
border: 1px solid #999999;
}
#cajadatos{
float: left;
width: 500px;
margin-left: 20px;
padding: 20px;
border: 1px solid #999999;
}

 

Estilos para las cajas en pantalla (messaging.css).
El código Javascript para el documento principal tiene que tomar el valor del campo
nombre del formulario y enviarlo como un mensaje al documento dentro del iframe
usando el método postMessage():
function iniciar(){
var boton=document.getElementById('boton');
boton.addEventListener('click', enviar, false);
}
function enviar(){
var nombre=document.getElementById('nombre').value;
var iframe=document.getElementById('iframe');
iframe.contentWindow.postMessage(nombre, '*');
}
window.addEventListener('load', iniciar, false);

 

Publicando un mensaje (messaging.js).
En el código del Listado 13-14, el mensaje fue compuesto por el valor del campo
nombre. El símbolo * fue usado como valor de destino para enviar este mensaje a
cualquier documento dentro del iframe (sin importar su origen).
Una vez que el botón “Enviar” es presionado, la función enviar() es llamada y el
valor del campo es enviado al contenido del iframe. Ahora es momento de tomar ese
mensaje y procesarlo. Recuerde que el documento destinado a ser abierto en un iframe es
exactamente igual a uno abierto en la ventana principal. El iframe simplemente simula una
ventana dentro del documento. Por este motivo, vamos a crear una pequeña plantilla
similar a la anterior pero solo con el propósito de mostrar la información recibida:
<!DOCTYPE html>
<html lang="es">
<head>
<title>iframe window</title>
<script src="iframe.js"></script>
</head>
<body>
<section>
<div><b>Mensaje desde la ventana principal:</b></div>

<div id="cajadatos"></div>
</section>
</body>
</html>

 

Plantilla para el iframe (iframe.html).
Esta plantilla tiene su propia cajadatos que será usada para mostrar el mensaje
recibido en la pantalla, y también su propio código Javascript para procesarlo:
function iniciar(){
window.addEventListener('message', receptor, false);
}
function receptor(e){
var cajadatos=document.getElementById('cajadatos');
cajadatos.innerHTML='mensaje desde: '+e.origin+'<br>';
cajadatos.innerHTML+='mensaje: '+e.data;
}
window.addEventListener('load', iniciar, false);

 

Procesando los mensajes desde el destino (iframe.js).
Acorde a lo que explicamos anteriormente, para escuchar los mensajes la API provee el
evento message y algunas propiedades. En el código del Listado 13-16, una escucha para
este evento fue agregada y la función receptor() fue declarada para responder al
mismo. Esta función muestra el contenido del mensaje usando la propiedad data e
información acerca del documento que lo envió usando el valor de origin.
Recuerde que este código pertenece al documento HTML del iframe, no al documento
principal del Listado 13-12. Estos son dos documentos diferentes con sus propios entornos,
objetivos y códigos (uno es abierto en la ventana principal y el otro dentro del iframe).
Hágalo usted mismo: Hay un total de cinco archivos que tienen que ser creados y
subidos al servidor para poder probar este ejemplo. Primero, cree un nuevo archivo
HTML con el código del Listado 13-12 que será nuestro documento principal. Este
documento también requiere el archivo messaging.css con los estilos del
Listado 13-13 y el archivo messaging.js con el código Javascript del Listado 13-
14. La plantilla del Listado 13-12 contiene un elemento <iframe> con el archivo
iframe.html como su fuente. Necesitará crear también este archivo y copiar en
su interior el código del Listado 13-15 y además generar el correspondiente archivo
iframe.js con los códigos del Listado 13-16. Suba todos los archivos a su servidor,
abra el primer documento HTML en su navegador y envíe su nombre o cualquier
texto al iframe usando el formulario en pantalla.
 

Filtros y múltiples orígenes
Lo que hemos hecho hasta ahora no es una práctica recomendable, especialmente si
consideramos asuntos de seguridad. El código en el documento principal envía un mensaje
a un iframe específico, pero no controla los documentos dentro de ese iframe que estarán
autorizados a leerlo (cualquier documento dentro del iframe podrá leer el mensaje). Del
mismo modo, el código de nuestro ejemplo para el iframe no controla el origen y procesa
todo mensaje recibido. Ambas partes del proceso de comunicación tienen que ser
mejoradas para prevenir abusos o errores.
En el siguiente ejemplo, vamos a corregir esta situación y estudiar el procedimiento a
seguir para responder a un mensaje del documento origen usando otra propiedad del
evento message llamada source.
<!DOCTYPE html>
<html lang="es">
<head>
<title>Cross Document Messaging</title>
<link rel="stylesheet" href="messaging.css">
<script src="messaging.js"></script>
</head>
<body>

<section id="cajaformulario">
<form name="formulario">
<p>Su nombre: <input type="text" name="nombre" id="nombre"
required></p>
<p><input type="button" name="boton" id="boton"
value="Enviar"></p>
</form>
</section>
<section id="cajadatos">
<iframe id="iframe" src="https://www.dominio2.com/iframe.html"
width="500" height="350"></iframe>
</section>
</body>
</html>

 

Comunicándonos con un origen/destino específicos.
Supongamos que el nuevo documento HTML con el código del Listado 13-17 está
localizado en www.dominio1.com, pero el código para el iframe es cargado desde una
segunda ubicación en www.dominio2.com. Para prevenir abusos y errores, necesitaremos
declarar estos dominios en el código y ser específicos sobre quién estará autorizado a leer
los mensajes y desde dónde.
En el código del Listado 13-17, no estamos solo cargando el archivo HTML para el
iframe sino declarando la ruta completa hacia otro servidor (www.dominio2.com). El
documento principal se encontrará en www.dominio1.com y el contenido del iframe en
www.dominio2.com. Los siguientes códigos consideran esta situación:
function iniciar(){
var boton=document.getElementById('boton');
boton.addEventListener('click', enviar, false);

window.addEventListener('message', receptor, false);
}
function enviar(){
var nombre=document.getElementById('nombre').value;
var iframe=document.getElementById('iframe');
iframe.contentWindow.postMessage(nombre, 'https://www.dominio2.com');
}
function receptor(e){
if(e.origin=='https://www.dominio2.com'){
document.getElementById('nombre').value=e.data;
}
}
window.addEventListener('load', iniciar, false);

 

Comunicándonos con orígenes específicos (messaging.js).
Preste atención a la función enviar() en el Listado 13-18. El método postMessage()
ahora declara un destino específico para el mensaje (www.dominio2.com). Solo documentos
dentro del iframe y que provengan de ese origen específico podrán leer este mensaje.
En la función iniciar() del Listado 13-18 también agregamos una escucha para el
evento message. El propósito de esta escucha y de la función receptor() es recibir la
respuesta enviada desde el iframe. Esto cobrará sentido en unos minutos.
Veamos ahora el código Javascript ejecutado en el iframe que nos ayudará a entender
cómo un mensaje proveniente de un origen específico es procesado y cómo respondemos
al mismo (usaremos exactamente el mismo documento HTML del Listado 13-15 para el
iframe).
function iniciar(){
window.addEventListener('message', receptor, false);
}

function receptor(e){
var cajadatos=document.getElementById('cajadatos');
if(e.origin=='https://www.dominio1.com'){
cajadatos.innerHTML='mensaje válido: '+e.data;
e.source.postMessage('mensaje recibido', e.origin);
}else{
cajadatos.innerHTML='origen inválido';
}
}
window.addEventListener('load', iniciar, false);

 

Respondiendo al documento principal (iframe.js).
Filtrar el origen es tan simple como comparar el valor de la propiedad origin con el
dominio del cual queremos leer los mensajes. Una vez que comprobamos que el origen es
válido, el mensaje es mostrado en pantalla y luego una respuesta es enviada de regreso
aprovechando el valor de la propiedad source. La propiedad origin es también usada
para declarar que esta respuesta estará solo disponible para la ventana que envió el
mensaje en primer lugar. Ahora puede regresar al Listado 13-18 para comprender cómo la
función receptor() procesará esta respuesta.
Hágalo usted mismo: Este último ejemplo es un poco engañoso. Estamos usando
dos orígenes diferentes, por lo que necesitará dos dominios diferentes (o
subdominios) para comprobar el funcionamiento de los códigos. Reemplace los
dominios declarados en los códigos por los suyos propios y luego suba los
archivos correspondientes al documento principal en uno y los correspondientes
al iframe en el otro. El documento principal cargará en el iframe el código desde
el segundo dominio y así podrá ver cómo funciona el proceso de comunicación
entre estos dos orígenes diferentes.
 

Web Sockets
En esta parte del capítulo, describiremos el último componente de lo que consideramos
API Communication. API WebSocket ofrece soporte para comunicaciones bidireccionales
entre navegadores y servidores. La comunicación es realizada a través de conexiones TCP,
sin enviar cabeceras HTTP, reduciendo de este modo la cantidad de datos transmitidos en
cada llamada. Además, la conexión es persistente, permitiendo a los servidores mantener
a los navegadores permanentemente informados. Esto significa que no deberemos
encargarnos de llamar al servidor a cada momento para obtener datos actualizados; en su
lugar, el servidor mismo de forma automática nos enviará información acerca de la
situación actual.
WebSocket puede ser considerado por algunos como una mejora de Ajax, pero es en
realidad una alternativa totalmente diferente de comunicación que permite la construcción
de aplicaciones en tiempo real en una plataforma escalable (por ejemplo, video juegos para
múltiples jugadores, salas de chat, etc…).
La API es simple. Solo unos pocos métodos y eventos son incluidos para abrir y cerrar
conexiones y enviar y escuchar por mensajes. Sin embargo, ningún servidor soporta este
protocolo por defecto. Debido a esto, necesitaremos instalar nuestro propio servidor WS
(servidor WebSocket) para poder establecer comunicación entre el navegador y el
servidor que aloja a la aplicación.
Configuración del servidor WS
Un programador experimentado seguramente podrá descubrir por sí mismo cómo
construir un servidor WS, pero para aquellos que deseamos dedicar nuestro tiempo libre a
actividades un tanto más recreativas, ya se encuentran disponibles en la web varios
códigos que nos permitirán configurar nuestro propio servidor WS y procesar conexiones
en unos pocos minutos. Dependiendo de sus preferencias, puede optar por códigos
programados en PHP, Java, Ruby, y otros.
IMPORTANTE: Al momento de escribir estas líneas, la especificación está siendo
mejorada y expandida debido a problemas de seguridad y aún no se encuentran
servidores WS disponibles que soporten estas mejoras. Para obtener una lista
completa, visite nuestro sitio web y siga los enlaces correspondientes a este
capítulo.
Los siguientes ejemplos estarán orientados al uso de un servidor sencillo programado
en PHP llamado phpwebsocket (code.google.com/p/phpwebsocket/). Este servidor utiliza
un único archivo llamado server.php que responde a una serie de códigos pre
programados, como veremos más adelante. El archivo debe ser subido a un servidor y
luego ejecutado por medio de un sistema del tipo Telnet como Putty, por ejemplo.
WebSocket usa una conexión persistente, por lo que el código del servidor WS tiene
que funcionar todo el tiempo, capturando solicitudes y enviando actualizaciones a los
navegadores conectados. Usando Putty puede acceder a su servidor desde una consola y
ejecutar los comandos necesarios para poner el servidor WS en marcha.
Hágalo usted mismo: En primer lugar debe contar con las herramientas adecuadas.
Instale su consola de acceso Telnet o descargue Putty desde www.chiark.
greenend.org.uk/~sgtatham/putty/. También necesita descargar el archivo
server.php del servidor phpwebsocket disponible en https://code.google.com/
p/phpwebsocket/. Modifique los datos de acceso dentro de este archivo (dominio o
IP de su servidor y puerto), y súbalo a su servidor. Desde la consola Telnet acceda al
servidor y ejecute el archivo con el siguiente comando: php –q server.php. Esto
pondrá en marcha el servidor WS.
IMPORTANTE: El servidor no solo se encarga de establecer la comunicación entre el
navegador y el servidor sino que además está a cargo de generar la respuesta
adecuada. La forma de construir y realizar esta respuesta está programada dentro
del mismo código del servidor. Deberá adaptar este código a las necesidades de su
aplicación.
Constructor
Antes de programar los códigos para interactuar con el servidor WS, veamos lo que la API
ofrece con este fin. La especificación declara solo una interface con unos pocos métodos,
propiedades y eventos, además de un constructor, para establecer la conexión:
WebSocket(url) Este constructor inicia una conexión entre la aplicación y el servidor WS
apuntado por el atributo url. Retorna un objeto WebSocket referenciando esta
conexión. Un segundo atributo puede ser especificado para proveer un array con sub
protocolos de comunicación.
Métodos
La conexión es iniciada por el constructor, por lo que solo necesitamos dos métodos para
utilizarla:
send(datos) Este es el método necesario para enviar un mensaje al servidor WS. El valor
del atributo datos representa los datos a ser transmitidos (normalmente una cadena
de texto).
close() Este método cierra la conexión.
Propiedades
Algunas propiedades no permiten conocer la configuración y el estado de la conexión:
url Muestra la URL a la cual la aplicación está conectada.
protocol Esta propiedad retorna el sub protocolo usado, si existe.
readyState Esta propiedad retorna un número representando el estado de la conexión: 0
significa que la conexión no ha sido aún establecida, 1 significa que la conexión fue
abierta, 2 significa que la conexión está siendo cerrada, y 3 significa que la conexión
fue cerrada.
bufferedAmount Esta es una propiedad extremadamente útil que nos permite conocer la
cantidad de datos requeridos pero aún no enviados al servidor. El valor retornado nos
ayuda a regular la cantidad de datos y la frecuencia de cada solicitud para evitar
saturar al servidor.

Eventos
Para conocer el estado de la conexión y escuchar por mensajes enviados por el servidor,
debemos usar eventos. La API ofrece los siguientes:
open Este evento es disparado cuando la conexión es abierta.
message Este evento es disparado cuando un mensaje proveniente del servidor se
encuentra disponible.
error Este evento es disparado cuando ocurre un error.
close Este evento es disparado cuando la conexión es cerrada.
Plantilla
El archivo server.php del servidor WS que usamos como ejemplo contiene una función
process() que procesa una pequeña lista de comandos predefinidos y envía de regreso
la respuesta apropiada. Para probar esta API, vamos a usar un formulario en el que
podremos ingresar uno de estos comandos y enviarlos al servidor:
<!DOCTYPE html>
<html lang="es">
<head>
<title>WebSocket</title>
<link rel="stylesheet" href="websocket.css">
<script src="websocket.js"></script>
</head>
<body>
<section id="cajaformulario">
<form name="formulario">
<p>Comando:<br><input type="text" name="comando"
id="comando"></p>
<p><input type="button" name="boton" id="boton"
value="Enviar"></p>
</form>
</section>
<section id="cajadatos"></section>
</body>
</html>
Listado 13-20. Insertando commandos.
También crearemos un archivo CSS llamado websocket.css con los siguientes
estilos:
#cajaformulario{
float: left;
padding: 20px;
border: 1px solid #999999;
}
#cajadatos{
float: left;
width: 500px;
height: 350px;
overflow: auto;
margin-left: 20px;
padding: 20px;
border: 1px solid #999999;
}
Listado 13-21. Estilos habituales para las cajas.
Iniciar la comunicación
Como siempre, el código Javascript es responsable de todo el proceso. En el siguiente
listado, crearemos nuestra primera aplicación de comunicaciones para entender la forma
de trabajo de esta API:
function iniciar(){
cajadatos=document.getElementById('cajadatos');
var boton=document.getElementById('boton');
boton.addEventListener('click', enviar, false);
socket=new WebSocket("ws://www.dominio.com:12345/server.php");
socket.addEventListener('message', recibido, false);
}
function recibido(e){
var lista=cajadatos.innerHTML;
cajadatos.innerHTML='Recibido: '+e.data+'<br>'+lista;
}
function enviar(){
var comando=document.getElementById('comando').value;
socket.send(comando);
}
window.addEventListener('load', iniciar, false);
Listado 13-22. Enviando mensajes al servidor.
En la función iniciar(), el objeto WebSocket es construido y almacenado en la
variable socket. El atributo url del constructor apunta hacia la ubicación del archivo
server.php en nuestro servidor, incluyendo el puerto de conexión (en este ejemplo,
12345). Generalmente la dirección del servidor WS será declarada con el valor IP
correspondiente al servidor (en lugar de un dominio) y un valor de puerto como 8000 u
8080, pero esto dependerá de sus necesidades, la configuración de su servidor, la ubicación
de su archivo, etc… El uso de la IP en lugar del dominio es una práctica recomendada para
evitar el proceso de traducción DNS. En cada una de las llamadas, la red realiza un proceso
de traducción de las direcciones web para obtener las direcciones reales de los servidores a
los que corresponden. Si en lugar de especificar un dominio declaramos directamente la
dirección IP de nuestro servidor, evitamos esta operación, estableciendo una comunicación
más fluida.
Luego de que obtenemos el objeto WebSocket, una escucha para el evento message es
agregada. Este evento será disparado cada vez que el servidor WS envíe un mensaje al
navegador. La función recibido() fue declarada para responder al mismo. Como en otras
API, este evento también incluye la propiedad data que retorna el contenido del mensaje.
En la función recibido(), usamos esta propiedad para mostrar el mensaje en pantalla.
Para enviar mensajes al servidor incluimos la función enviar(). El valor insertado en
el elemento <input> llamado comando es tomado por esta función y enviado al servidor
WS usando el método send().
IMPORTANTE: El archivo server.php contiene una función llamada process()
para procesar cada llamada y enviar una respuesta acorde a los datos recibidos.
Usted puede cambiar esta función para satisfacer las necesidades de su
aplicación, pero para nuestros ejemplos la hemos considerado exactamente
como es ofrecido en Google Codes. La función toma el valor del mensaje recibido
y lo compara con una lista de comandos predefinidos. Los comandos disponibles
en la versión que utilizamos para probar estos ejemplos son: hello, hi, name,
age, date, time, thanks, y bye. Por ejemplo, si enviamos el mensaje “hello”, el
servidor nos responderá “hello human”.
Aplicación completa
En nuestro primer ejemplo podemos ver cómo funciona el proceso de comunicación de
esta API. El constructor WebSocket inicia la conexión, el método send() envía cada
mensaje al servidor para ser procesado, y el evento message informa a la aplicación sobre
las respuestas recibidas. Sin embargo, no cerramos la conexión, no controlamos por
errores, e incluso no detectamos cuándo la conexión estaba lista para trabajar. Veamos
ahora un ejemplo que aprovecha todos los eventos provistos por la API para informar
sobre el estado de la conexión en cada paso del proceso.
function iniciar(){
cajadatos=document.getElementById('cajadatos');
var boton=document.getElementById('boton');
boton.addEventListener('click', enviar, false);
socket=new WebSocket("ws://www.dominio.com:12345/server.php");
socket.addEventListener('open', abierto, false);
socket.addEventListener('message', recibido, false);
socket.addEventListener('close', cerrado, false);
socket.addEventListener('error', errores, false);
}
function abierto(){
cajadatos.innerHTML='CONEXION ABIERTA<br>';
cajadatos.innerHTML+='Estado: '+socket.readyState;
}
function recibido(e){
var lista=cajadatos.innerHTML;
cajadatos.innerHTML='Recibido: '+e.data+'<br>'+lista;
}
function cerrado(){
var lista=cajadatos.innerHTML;
cajadatos.innerHTML='CONEXION CERRADA<br>'+lista;
var boton=document.getElementById('boton');
boton.disabled=true;
}
function errores(){
var lista=cajadatos.innerHTML;
cajadatos.innerHTML='ERROR<br>'+lista;
}
function enviar(){
var comando=document.getElementById('comando').value;
if(comando=='cerrar'){
socket.close();
}else{
socket.send(comando);
}
}
window.addEventListener('load', iniciar, false);
Listado 13-23. Informando el estado de la conexión.
Incluimos algunas mejoras en el código del Listado 13-23 comparado con el ejemplo
anterior. Escuchas para todos los eventos disponibles en el objeto WebSocket fueron
agregadas junto con las funciones apropiadas para responder a cada uno de ellos.
También mostramos el estado de la conexión cuando es abierta usando el valor de la
propiedad readyState, cerramos la conexión usando el método close() cuando el
comando “cerrar” es enviado por el usuario desde el formulario, y desactivamos el botón
“Enviar” cuando la conexión es cerrada (boton.disabled=true).
Hágalo usted mismo: Este último ejemplo requiere el documento HTML y los
estilos CSS de los Listados 13-20 y 13-21. Suba estos archivos a su servidor y, si aún
no lo ha hecho, ejecute el servidor WS con la instrucción php –q server.php
(como ya explicamos, esto es realizado desde una consola Telnet como Putty). Una
vez que el servidor es activado, abra el documento HTML en su navegador. Inserte
comandos en el formulario en pantalla y presione el botón “Enviar”. Debería
obtener respuestas del servidor de acuerdo al comando insertado (hello, hi,
El gran libro de HTML5, CSS3 y Javascript
304
name, age, date, time, thanks, o bye). Envíe el comando “cerrar” para cerrar la
conexión.
IMPORTANTE: Si el servidor no funciona correctamente, los errores producidos
serán retornados dentro de la consola Telnet. Controle esta consola para
descubrir inconvenientes en la conexión.
13.4 Referencia rápida
HTML5 incorpora tres API diferentes con propósitos de comunicación. XMLHttpRequest
Level 2 es una mejora del viejo objeto XMLHttpRequest usado para la construcción de
aplicaciones Ajax. La API Web Messaging ofrece un sistema de comunicación para
diferentes ventanas, pestañas, iframes, o incluso otras APIs. Y la API WebSocket provee
una nueva alternativa para establecer una conexión rápida y efectiva entre navegadores y
servidores.
XMLHttpRequest Level 2
Esta API tiene un constructor para objetos XMLHttpRequest y algunos métodos, propiedades y
eventos para procesar la conexión.
XMLHttpRequest() Este constructor retorna el objeto XMLHttpRequest que necesitamos
para iniciar y procesar una conexión con el servidor.
open(método, url, asíncrono) Este método abre la conexión entre la aplicación y el
servidor. El atributo método determina qué método HTTP será usado para enviar la
información (GET o POST). El atributo url declara la ruta hacia el código que recibirá y
procesará esta información. Y el atributo asíncrono es un valor booleano que
establece si la conexión será síncrona o asíncrona (true para asíncrona).
send(datos) Este método envía el valor del atributo datos al servidor. El atributo datos
puede ser un ArrayBuffer, un blob, un documento, una cadena de texto o un objeto
FormData.
abort() Este método cancela la solicitud.
timeout Esta propiedad establece el tiempo en milisegundos que tiene la solicitud para ser
procesada.
readyState Esta propiedad retorna un valor representando el estado de la conexión: 0
significa que el objeto fue construido, 1 significa que la conexión fue abierta, 2 significa
que la cabecera de la respuesta ha sido recibida, 3 significa que la respuesta está
siendo recibida, 4 significa que la transmisión de datos ha sido completada.
responseType Esta propiedad retorna el tipo de respuesta. Puede ser declarada para
cambiar el tipo de respuesta. Los posibles valores son arraybuffer, blob,
document, y text.
response Esta propiedad retorna la respuesta a la solicitud en el formato declarado por la
propiedad responseType.
API Communication
305
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.
loadstart Este evento es disparado cuando la solicitud es iniciada.
progress Este evento es disparado periódicamente mientras la solicitud es procesada.
abort Este evento es disparado cuando la solicitud es abortada.
error Este evento es disparado cuando ocurre un error.
load Este evento es disparado cuando la solicitud ha sido completada exitosamente.
timeout Este evento es disparado cuando la solicitud toma más tiempo en ser procesada
que el especificado por la propiedad timeout.
loadend Este evento es disparado cuando la solicitud ha sido completada (en ambos
casos, éxito o error).
Un atributo especial fue incluido para obtener un objeto XMLHttpRequestUpload en
lugar del objeto XMLHttpRequest con el propósito de subir datos al servidor.
upload Este atributo retorna un objeto XMLHttpRequestUpload. Este objeto usa los mismos
métodos, propiedades y eventos de un objeto XMLHttpRequest pero con el propósito de
procesar la subida de archivos.
La API también incluye una interface para crear objetos FormData representando
formularios HTML.
FormData() Este constructor retorna un objeto FormData para representar un formulario
HTML.
append(nombre, valor) Este método agrega datos a un objeto FormData. Cada dato agregado
al objeto representa un campo de formulario, con su nombre y valor declarado en los
atributos. Una cadena de texto o un blob pueden ser provistos para el atributo valor.
Esta API usa la interface ProgressEvent (también usada por otras APIs para controlar el
progreso de una operación) que incluye las siguientes propiedades:
lengthComputable Esta propiedad es un valor booleano que determina si los valores del
resto de las propiedades son válidos.
loaded Esta propiedad retorna la cantidad total de bytes ya descargados o subidos.
total Esta propiedad retorna el tamaño total en bytes de los datos que están siendo
descargados o subidos.
API Web Messaging
Esta API está constituida solo por una interface que provee métodos, propiedades y
eventos para comunicar entre sí aplicaciones ubicadas en diferentes ventanas, pestañas,
iframes o incluso otras API.
El gran libro de HTML5, CSS3 y Javascript
306
postMessage(mensaje, destino) Este método envía un mensaje a una contentWindow
específica y al documento declarado como destino por el atributo destino. El atributo
mensaje es el mensaje a ser transmitido.
message Este evento es disparado cuando un mensaje es recibido.
data Esta propiedad del evento message retorna el contenido del mensaje recibido.
origin Esta propiedad del evento message retorna el origen del documento que envió el
mensaje.
source Esta propiedad del evento message retorna una referencia a la ventana desde la
que el mensaje fue enviado.
API WebSocket
Esta API incluye un constructor que retorna un objeto WebSocket e inicia la conexión.
Además, provee algunos métodos, propiedades y eventos para controlar la comunicación
entre el navegador y el servidor.
WebSocket(url) Este constructor retorna un objeto WebSocket e inicia la conexión con el
servidor. El atributo url declara la ruta del código del servidor WS y el puerto de
comunicación. Un array con sub protocolos puede ser especificado como un segundo
atributo.
send(datos) Este método envía un mensaje al servidor WS. El atributo datos debe ser una
cadena de texto con el mensaje a ser enviado.
close() Este método cierra la conexión con el servidor WS.
url Esta propiedad muestra la URL que la aplicación está usando para conectarse al
servidor WS.
protocol Esta propiedad retorna el sub protocolo usado por la conexión, si existe.
readyState Esta propiedad retorna un valor representando el estado de la conexión: 0
significa que la conexión no ha sido aún establecida, 1 significa que la conexión fue
abierta, 2 significa que la conexión está siendo cerrada, y 3 significa que la conexión
fue cerrada.
bufferedAmount Esta propiedad retorna la cantidad total de datos que esperan ser
enviados al servidor.
open Este evento es disparado cuando la conexión es abierta.
message Este evento es disparado cuando el servidor envía un mensaje a la aplicación.
error Este evento es disparado cuando ocurre un error.
close Este evento es disparado cuando la conexión es cerrada.