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>
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
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>
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
Seleccionar nodos Por id Por etiqueta var noticias = document.getelementbyid("noticias") //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)")
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
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
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);
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);
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
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);
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
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>
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); )
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
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: *
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
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
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)