Tema 2. Programación en el cliente con Javascript 2.3 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 2015-16 / U. Alicante API DOM estándar (Level 1) • Se manipulan los nodos del árbol, lo que a su vez “modifica” el HTML • Muy potente, pero también algo tedioso de utilizar <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 2015-16 / U. Alicante Manipular directamente 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 2015-16 / U. Alicante Modificar el HTML • insertAdjacentHTML: método para poder insertar HTML antes/ en medio/después de una etiqueta HTML • insertAdjacentHTML(posicion, texto) • posicion: “beforebegin”, “afterbegin”, “beforeend”, “afterend” • texto: se evaluará y convertirá a HTML <div id="texto">Hola </div> <button id="boton">Añadir</button> <script> document.getElementById("boton").onclick = function() { var texto = document.getElementById("texto"); texto.insertAdjacentHTML("beforeend", "<b>mundo</b>"); texto.insertAdjacentHTML("afterend", "<div>más texto</div>"); } </script> http://jsbin.com/dovoxecipu Aplicaciones Distribuídas en Internet 2015-16 / U. 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 2015-16 / U. Alicante jQuery • El framework que popularizó la idea de usar selectores CSS para seleccionar nodos del DOM $(‘img.icono’) .hide(3000) .addClass(‘oculto’) • Además de esto jQuery tiene muchas otras funcionalidades, y sobre todo proporciona una capa de compatibilidad con navegadores no estándar Aplicaciones Distribuídas en Internet 2015-16 / U. Alicante Tema 2. Programación en el cliente con Javascript 2.4 AJAX AJAX • Asynchronous Javascript And XML • Combinación de tecnologías: • XMLHttpRequest: hacer peticiones al servidor con Javascript y recibir la respuesta sin recargar la página ni cambiar de página • Formatos JSON/XML: recibir información estructurada en la respuesta • API DOM: actualizar solo parte de la página con datos procedentes del servidor • Se convirtió en una de las características distintivas de las aplicaciones “web 2.0” Aplicaciones Distribuídas en Internet 2015-16 / U. Alicante Código asíncrono • Javascript usa un único hilo, si la ejecución se bloqueara hasta que respondiera el servidor, mientras tanto no podríamos hacer nada más var req = new XMLHttpRequest(); //preparar la petición. El tercer parámetro indica que 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(); console.log('Esto se ejecutará inmediatamente después del send()'); //Ejemplo de petición POST var req = new XMLHttpRequest(); req.open('POST', 'http://www.miservidor.com/miprograma.php', true); req.onreadystatechange = mi_callback; //Si enviamos parámetros HTTP esto es necesario. Si enviamos JSON no. req.setRequestHeader(“Content-type","application/x-www-form-urlencoded") //Los datos se envían en el send req.send('cod='+codigo); Aplicaciones Distribuídas en Internet 2015-16 / U. Alicante El callback • Para informar del progreso puede llamarse varias veces con distintos valores de la propiedad readyState (valores entre 2 y 4). Normalmente nos interesa el 4, hasta entonces la respuesta no se ha recibido entera function mi_callback() { if ((this.readyState == 4) && (this.status == 200)) console.log(this.responseText); } //callback definido “sobre la marcha”, como una función anónima xhr.onreadystate = function() { //podemos usar la clausura generada para referenciar variables “externas” if ((xhr.readyState == 4) && (xhr.status == 200)) console.log(xhr.responseText); } Aplicaciones Distribuídas en Internet 2015-16 / U. Alicante ¿Pero dónde está XML? • En el 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 2015-16 / U. Alicante JSON y AJAX • JSON 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++) { console.log("A las " + mensajes[i].hora + " " + mensajes[i].login + " dijo: " + mensajes[i].texto); } Aplicaciones Distribuídas en Internet 2015-16 / U. Alicante Peticiones REST con JS • Firmar una petición en un API tipo change.org xhr.open('POST', 'api/peticiones/' + idPeticion + "/firmas" , true) xhr.onreadystatechange = function() { ... } xhr.setRequestHeader("Content-type", "application/json") var firma = {}; firma.email = document.getElementById("email").value; firma.comentario = document.getElementById("comentario").value ... firma.publica = document.getElementById(“publica").checked; xhr.send(JSON.stringify(firma)) Aplicaciones Distribuídas en Internet 2014-15 / U. 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 en navegadores modernos (>=IE10) • 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 2015-16 / U. 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.addEventListener("progress", verProgreso) xhr.addEventListener("load", function() { alert(this.responseText) }); xhr.addEventListener("error", function() { alert("error: " + this.status); }); xhr.open("POST", "http://loquesea.com/imagenes", true) xhr.setRequestHeader("Content-type", "multipart/form-data") xhr.send(fdata) } </script> <form 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 2015-16 / U. Alicante Fetch API • Sustituto moderno de XMLHttpRequest • Usa promesas en lugar de callbacks • En “proceso de implantación” fetch('./api/some.json') .then( function(response) { if (response.status !== 200) { console.log('Looks like there was a problem. Status Code: ' + response.status); return; } // Examine the text in the response response.json().then(function(data) { console.log(data); }); } ) .catch(function(err) { console.log('Fetch Error :-S', err); }) Aplicaciones Distribuídas en Internet 2015-16 / U. 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 • Por ejemplo, el Javascript de una página de www.vuestrositio.com en principio no puede hacer peticiones AJAX a Facebook • En realidad la petición se hará, pero el navegador no nos dará acceso al resultado Aplicaciones Distribuídas en Internet 2015-16 / U. Alicante Cross-Domain AJAX • Estándar CORS (Cross Origin Resource Sharing): permite saltarse la same origin policy con la colaboración del servidor • En cada petición cross-domain el navegador envía una cabecera Origin con el origen de la petición. Es imposible falsearla desde JS • El servidor puede enviar una cabecera Access-Control-Allow-Origin indicando los orígenes desde los que se puede acceder a la respuesta. Si encajan con el origen del XMLHttpRequest el navegador dará “luz verde” HTTP/1.1 200 OK Server: Apache/2.0.61 Access-Control-Allow-Origin: * Aplicaciones Distribuídas en Internet 2015-16 / U. Alicante El tag <script> y la seguridad • Las restricciones de seguridad no se aplican a la etiqueta <script>. Con ella podemos cargar (¡¡y ejecutar!!) código Javascript de cualquier origen • En lugar de cargar un script podríamos cargar en un punto del documento la respuesta del servidor en JSON a una llamada a un API, por ejemplo. Solo nos falta disparar un JS para poder procesar estos datos <script src=“https://api.flickr.com/services/rest? format=json&method=flickr.photos.search&tags=gatitos&api_key=8bd6 dfb55bc750946f80606ef5aefaca”> </script> Aplicaciones Distribuídas en Internet 2014-15 / U. Alicante JSONP • Si consiguiéramos ejecutar una función nuestra que recibiera como parámetro el JSON que envía el servidor todo estaría resuelto • En los servicios que admiten JSONP, debemos pasar un parámetro (normalmente se llama “callback” o algo similar) con el nombre de la función a llamar http://api.flickr.com/services/rest? format=json&method=flickr.photos.search&api_key=<TU_API_KE Y>&tags=gatitos&jsoncallback=miFuncion • El servidor devolverá un resultado del estilo miFuncion(JSON_RESULTADO_DE_LA_PETICION) Aplicaciones Distribuídas en Internet 2014-15 / U. Alicante Ejecutando JSONP “a petición” • Una etiqueta <script> creada dinámicamente se ejecuta en el momento en que se inserta en el documento <script> document.getElementById(“buscar").onclick = hacerBusqueda() { var tags = document.getElementById("tags").value miScript = document.createElement("script") miScript.src = "https://api.flickr.com/services/rest? format=json&method=flickr.photos.search&api_key=8bd6dfb55bc750946f80606 ef5aefaca&jsoncallback=miCallback&tags=" + tags document.body.appendChild(miScript) } function miCallback(json){alert(JSON.stringify(json))} </script> <input type=“text” id=“tags”> <button id=“buscar”>Buscar</button> http://jsbin.com/doniqorete Aplicaciones Distribuídas en Internet 2014-15 / U. Alicante Tema 2. Programación en el cliente con Javascript 2.5 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 2015-16 / U. 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 2015-16 / U. 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 2015-16 / U. Alicante