Tema 3, Programación en el cliente con Javascript Parte III: APIs estándar para la Web APIs estándar vs. librerías • Los navegadores incorporan gran número de APIs estándar que convierten a Javascript+HTML5+CSS en una potente plataforma • Manipulación del HTML (DOM), Comunicación con el servidor (AJAX, websocketsen tiempo real-), Multithreading (web workers), Acceso a hardware (geolocalización, batería), Media Capture API (captura de audio/video), Gráficos 2D/ 3D, Bases de datos,... • Es muy habitual el uso de librerías Javascript para simplificar la programación y mejorar la productividad • Ejemplos: jQuery, MooTools, YUI, ExtJS,... • Razones: • Algunos APIs estándar son complicados de usar • Incompatibilidades con el estándar sobre todo con IE. Las librerías nos dan una capa de abstracción que oculta estas incompatibilidades • Hay algunas cosas que todavía no están en ningún API estándar (p.ej. widgets) Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante Tema 3. Programación en el cliente con Javascript Parte III: APIs estándar para la Web 1. Manejo del DOM El DOM • Como ya hemos visto, por cada etiqueta HTML existe un objeto Javascript equivalente • Es decir, el navegador mantiene en memoria un modelo orientado a objetos del documento que refleja la estructura del HTML • El DOM es un árbol. Cada “componente” del HTML es un nodo • Los cambios en el DOM se reflejan en “tiempo real” en el HTML <!DOCTYPE html> <html> <head> <title>Ejemplo de DOM</title> </head> <body> <!-- es un ejemplo un poco simple --> <p style=“color:red”>Bienvenidos al <b>DOM</b></p> </body> </html> Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante Seleccionar nodos • Por id var noticias = document.getElementById("noticias") • Por etiqueta //Reducir el tamaño de todas las imágenes a la mitad var imags = document.getElementsByTagName("img"); for(var i=0; i<imags.length; i++) { imags[i].width /= 2; imags[i].height /= 2; } • Usando selectores CSS //Obtener el 1er nodo que cumple la condición var primero = document.querySelector(".destacado"); //Obtenerlos todos var nodos = document.querySelectorAll(".destacado"); //Cambiamos la clase. Nótese que es “className”, no “class” for (var i=0; i<nodos.length; i++) { nodos[i].className = "normal"; } //selectores un poco más complicados var camposTexto = document.querySelectorAll('input[type="text"]'); var filasPares = document. querySelectorAll("tr:nth-child(2n)") Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante Modificar el HTML • innerHTML: propiedad que nos permite leer/modificar el código HTML que hay dentro de una etiqueta • No es estándar en HTML4, pero sí en HTML5 • No se puede (debe) usar para editar tablas. Para eso existen métodos alternativos (ver p.ej. http://msdn.microsoft.com/en-us/ library/ms532998(v=vs.85).aspx) <input type="button" value="Pon texto" onclick="ponTexto()"/> <div id="texto"></div> <script type="text/javascript"> function ponTexto() { var mensaje = prompt("Dame un texto y lo haré un párrafo") var miDiv = document.getElementById("texto") miDiv.innerHTML += "<p>" + mensaje + "</p>" } </script> Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante API DOM estándar • Muy potente, pero también algo tedioso de utilizar • A diferencia de innerHTML, que permite manipular directamente el texto, se manipulan los nodos del árbol, lo que a su vez “modifica” el HTML <input type="button" value="Añadir párrafo" id="boton"/> <div id="texto"></div> <script type="text/javascript"> document.getElementById("boton").onclick = function() { var texto = prompt("Introduce un texto para convertirlo en párrafo"); /* Nótese que la etiqueta <p> es un nodo, y el texto que contiene es OTRO nodo, de tipo textNode, hijo del nodo <p> */ var p = document.createElement("P"); var nodoTexto = document.createTextNode(texto); p.appendChild(nodoTexto); document.body.appendChild(p); }; </script> http://jsbin.com/Uwoduf/1/watch?html,js,output Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante Tema 3. Programación en el cliente con Javascript Parte III: APIs estándar para la Web 2. AJAX AJAX • Asynchronous Javascript And XML • Combinación de tecnologías que permite: • Hacer peticiones al servidor con javascript y recibir la respuesta sin recargar la página ni cambiar de página • Parsear la respuesta y convertirla en objetos Javascript • Con esta información y el DOM se puede actualizar solo parte de la página con datos procedentes del servidor • El API existe desde hace bastantes años tanto en IE como en Firefox, pero lo “pusieron de moda” en Google (suggest, Gmail,…) • Se convirtió en una de las características distintivas de las aplicaciones “web 2.0” Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante Esquema AJAX síncrono Función que hace AJAX Petición HTTP XMLHttpRequest Respuesta HTTP Cambios en el HTML con innerHTML o equiv. Javascript Servidor web Evento que dispara AJAX Área dentro del HTML HTML Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante Código síncrono • ACLARACION: El AJAX normalmente es asíncrono (¿si no, qué significa la primera “A” del acrónimo?), aunque veremos primero este caso por ser más simple • Paso 1: Crear el objeto XMLHttpRequest • Paso 2: Realizar la petición • Método open: Prepararla (diferente según sea GET/POST y síncrona/asíncrona). En caso de ser GET los parámetros van aquí • Método send: Enviarla (en caso de ser POST los parámetros van aquí) • Paso 3: Procesar la respuesta • La propiedad status del objeto XMLHttpRequest contiene el código de estado del servidor (en HTTP, OK es el 200) • La respuesta del servidor la tenemos en la propiedad responseText Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante Ejemplo de código síncrono • Supongamos que en el servidor esperan un parámetro “cod” con un valor que tenemos en la variable Javascript “codigo”. var req = new XMLHttpRequest(); //preparar la petición. El tercer parámetro indica que no es asíncrona req.open('GET', 'http://www.miservidor.com/miprograma.php?cod=' + codigo, false); //Enviar la petición. Con GET el argumento siempre es null //Aquí se bloquearía hasta que llegue la respuesta del servidor req.send(null); //comprobar el código de estado if(req.status == 200) //responseText es la respuesta del servidor. //Normalmente haremos algo más útil con lo que devuelve el servidor que un alert alert(req.responseText) versión con GET //En la versión con POST sería todo igual excepto “open” y “send” //con POST los argumentos no van en la URL, sino en el “send” … req.open('POST', 'http://www.miservidor.com/miprograma.php', false); req.setRequestHeader("Content-type","application/x-www-form-urlencoded"); req.send('cod='+codigo); versión con POST Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante Esquema AJAX Función que hace AJAX Petición HTTP XMLHttpRequest Función “Callback” Cambios en el HTML con innerHTML o equiv. Respuesta HTTP Servidor web Javascript Evento que dispara AJAX Área dentro del HTML HTML Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante Código asíncrono • El Javascript puede seguir haciendo “otras cosas” mientras se recibe la respuesta del servidor. • En el caso síncrono el usuario podría pensar que el navegador se ha bloqueado o se ha colgado el script, si la respuesta del servidor tarda mucho • Hay que poner el tercer parámetro del método open a true (la petición sí es asíncrona) • El servidor nos avisará “llamando a la función que le designemos” (función “callback”) • En la propiedad onreadystatechange del XMLHttpRequest “apuntaremos” a la función • El callback no se llamará una sola vez, sino varias. El servidor nos informa en la propiedad readyState de XMLHttpRequest de si la respuesta ha empezado a llegar (solo cabeceras HTTP: readyState==2), está cargándose (readyState==3) o completa (readyState==4). Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante Ejemplo de código asíncrono • Supongamos que en el servidor esperan un parámetro “cod” con un valor numérico que tenemos en la variable Javascript “codigo”. var req = new XMLHttpRequest(); //preparar la petición. El tercer parámetro indica que sí es asíncrona req.open('GET', 'http://www.miservidor.com/miprograma.php?cod=' +codigo, true); //decir qué función hace de “callback”. Esto no se debe hacer antes del open req.onreadystatechange = mi_callback; req.send(null); //ya podemos seguir con otras cosas, este send no se bloquea //en algún sitio del javascript debe estar definida esta función function mi_callback() { if (req.readyState == 4) { //también valdría this.readyState, aunque esto no parece if(req.status == 200) //estar documentado (idem this.status, this.responseText) alert(req.responseText); } Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante responseText • responseText es la información que nos envía el servidor, en un String. • El servidor nos tendrá que enviar la información en un formato adecuado que podamos parsear desde Javascript • Por ejemplo, en un chat nos podría mandar los nuevos mensajes recibidos simplemente poniendo un mensaje en cada línea y separando sus datos (hora, usuario, texto) con algún carácter especial • Ya veremos cómo se hace desde el servidor para generar esta información, por el momento supongamos que lo hace 10:00:05#pepito#”hola a todos…” 10:00:15#jorgito#”hola pepito, cuánto tiempo sin saber de ti! :)” Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante ¿Pero dónde está XML? • En la idea de AJAX original, la información se enviaría en XML, que permite estructurar los datos de manera más “elegante” que un formato “casero” ad-hoc <chat> <mensaje> <hora>10:00:05</hora> <login>Pepito</login> <texto>Hola a todos…</texto> </mensaje> … </chat> • Problema: aunque el API DOM de Javascript permite parsear XML, resulta tedioso de usar Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante JSON y AJAX • AJAX es mucho más fácil de tratar que XML, gracias a JSON.parse o a eval [ {"hora":"10:00:05", "login":"pepito", "texto":"hola a todos…"}, {"hora":"10:00:15", "login":"jorgito", "texto":"hola pepito, cuánto tiempo sin saber de ti! :)"} ] mensajes = JSON.parse(req.responseText) for(i=0; i<mensajes.length; i++) { cout.log("A las " + mensajes[i].hora + " " + mensajes[i].login + " dijo: " + mensajes[i].texto); } Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante Restricciones de seguridad • Política de seguridad del “mismo origen”: un XMLHttpRequest solo puede hacer una petición AJAX al mismo host del que vino la página en la que está definido • Resumiendo, una página que tenéis en localhost y que tiene Javascript no puede hacer peticiones AJAX a Google, por ejemplo • El “cross-domain AJAX” (CORS) permite romper esta política bajo ciertas circunstancias • Si el servidor al que le haces la petición la permite (enviando la cabecera AccessControl-Allow-Origin), el navegador también dejará que se haga HTTP/1.1 200 OK Server: Apache/2.0.61 Access-Control-Allow-Origin: * Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante AJAX “nivel 2” • “Versión 2.0” de XMLHttpRequest que permite • Intercambiar datos binarios con el servidor. Por ejemplo, esto puede servir para subir imágenes o archivos en general • Acceder a ciertos eventos, por ejemplo para ir monitorizando el progreso en el envío/recepción de datos • Solo funciona en navegadores modernos (aunque no en IE9, consultar caniuse.com u otro recurso equivalente • API • FormData representa los campos de un formulario, incluyendo type=”file”. Enviando el FormData enviamos también el archivo • Hay varios eventos como “progress”, “load”,”error” o “abort” • La gestión de los eventos se hace con el estándar W3C de event listeners, sobre el objeto XMLHttpRequest Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante Ejemplo de AJAX Level 2 <script> function verProgreso(e) { var progreso= document.getElementById("progreso"); progreso.innerHTML = Math.round((e.loaded / e.total)*100)+"%"; } function uploadAJAX() { var fdata = new FormData(document.getElementById("formu")) var xhr = new XMLHttpRequest(); xhr.upload.addEventListener("progress", verProgreso, false) xhr.open("POST", "http://loquesea.com/upload", true) xhr.onreadystatechange = function() { if (this.readyState==4) alert(this.responseText)} xhr.send(fdata) } </script> ... <form enctype="multipart/form-data" id="formu"> Elegir archivo: <input type="file" name="archivo"/> <br/> <input type="button" value="enviar" onclick="uploadAJAX()"/> </form> <div id="progreso"></div> Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante Tema 3. Programación en el cliente con Javascript Parte III: APIs estándar para la Web 3. Animaciones Cambiar estilo dinámicamente • Recordar que en el atributo style de cualquier etiqueta se pueden especificar propiedades CSS • La propiedad Javascript equivalente style es un objeto cuyas propiedades son equivalentes a propiedades CSS • Los “-” de las propiedades CSS se sustituyen por su versión camelCase. Por ejemplo la propiedad CSS background-color se convierte en backgroundColor • Los valores leídos se obtienen como Strings aunque sean números http://jsbin.com/aJoWaPI/2/watch?html,output <p id="texto" style="font-size:12px">Hola</p> <input type="button" value="Aumentar" id="boton"> <script> function aumentar(idElemento) { elemento = document.getElementById(idElemento) return function() { elemento.style.fontSize = parseInt(elemento.style.fontSize) + 2 + "px"; } } document.getElementById("boton").onclick = aumentar('texto') </script> Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante Animaciones • Por desgracia, el siguiente código no funcionará nada bien • No se ven los pasos intermedios ¿por qué? (pista: no es que la animación vaya demasiado rápido) <p id="texto" style="font-size:1px">Hola</p> <input type="button" value="Animar" id="boton"> <script> function animar(idElemento) { elemento = document.getElementById(idElemento) return function() { //Vamos incrementando el tamaño del texto de 2 a 100 for(i=2;i<100;i++) { elemento.style.fontSize = i + “px”; } } } document.getElementById("boton").onclick = animar('texto') </script> Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante Solución: temporizadores • Permiten ejecutar código transcurrido un cierto intervalo de tiempo. Mientras Javascript no está ejecutándose se puede actualizar la pantalla • Tipos • timeout: 1 sola vez, pasados X ms • interval: indefinidamente, cada X ms • Animación: • Implementamos una función que ejecute un solo “frame” de la animación • Cada X ms (tiempo entre frames) llamamos a esta función (con un interval). O bien la llamamos transcurridos X ms y hacemos que se vuelva a llamar a sí misma de nuevo transcurridos X ms (con un timeout) Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante Animación con temporizadores • Implementamos una función que ejecute un solo “frame” de la animación • Animación en sí • Cada X ms (tiempo entre frames) llamamos a esta función (con un interval). • O bien la llamamos transcurridos X ms y hacemos que se vuelva a llamar a sí misma de nuevo transcurridos X ms (con un timeout) <div id="texto" style="position:relative">Hola</div> <script> function animar(id){ var elemento = document.getElementById(id); var pos = 0; var timer = setInterval(function() { if (pos < 200) { elemento.style.left = pos + "px"; pos++; } else clearInterval(timer); }, 20); } animar('texto') </script> http://jsbin.com/IHOVUqA/2/watch?html,js,output Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante Problemas de los temporizadores • Resolución limitada • En el propio estándar HTML5 se especifican 4 ms • No hay multithreading • Cuando se dispara un timeout si el navegador está ocupado lo pondrá “en cola” • 2 interval se pueden acumular y ejecutarse juntos, si cuando se está ejecutando uno se dispara el siguiente. • El navegador intentará no poner en la cola más de 1 vez el mismo interval para evitar que se acumulen demasiados. • Solución: requestAnimationFrame http://ejohn.org/blog/how-javascript-timers-work/ Ver por ejemplo: http://msdn.microsoft.com/es-ES/library/ie/hh920765.aspx Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante Transiciones CSS • En CSS3 se pueden hacer animaciones simples, cambiando gradualmente el valor de alguna/s propiedad/es • Todavía está en proceso de estandarización. Solo funciona en versiones recientes de Chrome/Safari, Opera y Firefox (4.0) • (!) En cada navegador estas propiedades CSS tienen un nombre distinto • Ventaja: no tienen problemas de temporización (¡suaaaaves! :) ) y están aceleradas por hardware (OK en móviles). • Limitación: solo para efectos sencillos p { background-color:white; } /* cuando se pase el ratón por encima, el color de fondo cambiará gradualmente a amarillo, en 2 segundos */ p:hover { background-color: yellow; -webkit-transition: background-color 2s; /* sintaxis chrome/safari */ } Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante Mientras el estándar se “estabiliza”... • Cada navegador usa sus propios nombres de propiedades, así aunque el estándar cambiara se nos asegura que la implementación propia de cada navegador no lo hará • Pero eso nos fuerza a usar la sintaxis “oficial” más la de cada uno de los navegadores por separado p { background-color:white; } p:hover { background-color: yellow; -webkit-transition: background-color 2s; -moz-transition: background-color 2s; -o-transition: background-color 2s; transition: background-color 2s; } /* /* /* /* Chrome/safari */ Firefox */ Opera */ estándar */ Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante Mezclando CSS3 con Javascript • Usando las propiedades Javascript equivalentes a las de CSS <div id="box" style="background-color:yellow; width:500px; height:100px; overflow:hidden;"> Esto debería encogerse hasta desaparecer al hacer click </div> <input type="button" value="animar" onclick="transicion()"> <script type="text/javascript"> function transicion() { var box = document.getElementById('box'); //en CSS se pueden transicionar varias propiedades //simultáneamente, separándolas por comas box.style.webkitTransition = 'width 3s, height 3s'; box.style.width = '0px'; box.style.height = '0px'; } </script> Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante Tema 3. Programación en el cliente con Javascript Parte III: APIs estándar para la Web 4. Local Storage Local Storage API • Permite compartir variables entre páginas • Características: • Se almacenan pares “clave=valor” • Aunque la especificación no restringe el tipo para el valor, por el momento todos los navegadores lo almacenan como String • Esto quiere decir que al recuperarlo tendremos que convertirlo al tipo original. • Un enfoque muy típico es usar JSON, así podemos guardar objetos • Convertir de objeto a cadena: JSON.stringify(objeto) • de cadena a objeto: JSON.parse(cadena) • Hay dos tipos de almacenamiento • Objeto localStorage: el ámbito del dato es el sitio web. Se conserva aunque se cierre el navegador • Objeto sessionStorage: el ámbito es la ventana (o solapa, si tenemos varias abiertas). Se conserva hasta que ésta se cierre Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante API básico de Local Storage • getItem(clave) • setItem(clave,valor) • length: propiedad de solo lectura que indica cuántos pares clave/ valor hay almacenados • key(i): devuelve el nombre de la clave i-ésima (para poder recuperar su valor con getItem) • clear(): eliminar todos los datos Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante Ejemplo function guardarNombre() { nombre = prompt("¿cómo te llamas?") localStorage.setItem("usuario", nombre) //esta sintaxis es equivalente a lo anterior localStorage.usuario = nombre //y esta también localStorage["usuario"] = nombre edad = prompt("¿Cuántos años tienes?") localStorage.setItem("edad", edad) } function mostrarNombre() { alert("Me acuerdo de ti, " + localStorage.usuario + " vas a cumplir " + parseInt(localStorage.edad) + 1 !! +"años") } function mostrarTodosLosDatos() { datos="" for(var i=0; i<localStorage.length; i++) { clave = localStorage.key(i) datos = datos + clave + "=" + localStorage[clave] + '\n' } alert("LocalStorage contiene " + datos) } Aplicaciones Distribuídas en Internet 2013-­‐14 / Univ. Alicante