JavaScript

En este tema vamos a aprender a programar en JavaScript. Existe gran cantidad de documentación de calidad sobre JavaScript, ya que se trata de un lenguaje ampliamente usado y que además tiene ya cerca de 20 años. Por ello, en este tema daré referencias a documentos donde se explican las diferentes cuestiones a tratar.

Primeros pasos

Los primeros pasos los vamos a dar con Librosweb.es. Esta primera parte abarca los siguientes capítulos:

Ampliación de manejo de cadenas de texto

Las siguientes funciones para cadenas de texto también son interesantes:

Replace

La función replace está descrita en http://www.w3schools.com/jsref/jsref_replace.asp

La función replace solamente sustituye la primera aparición, aunque sólo sea parte de una palabra. Es algo que se puede observar en el siguiente ejemplo:

var myString = "loremipsum lorem lorem"; var myCleanedUpString = myString.replace("lorem", "dolor"); // doloripsum lorem lorem

Una posible solución a este problema la tenemos en el siguiente código:

var myString = "loremipsum lorem lorem"; var myCleanedUpString = myString; while (myCleanedUpString.indexOf("lorem")>-1){ myCleanedUpString = myCleanedUpString.replace("lorem", "dolor"); }

Match

La referencia a match está en http://www.w3schools.com/jsref/jsref_match.asp

Otro ejemplo puede verse con el siguiente código:

var cadena = "Perro, Perros, gato, Pedro"; var regex = /e.ro/g; // /e.ro/g es equivalente a new RegExp("e.ro","gi"); var arrayCoincidencias = cadena.match(regex); --> ["Perro","Perro","Pedro"] console.log(arrayCoincidencias);

Las expresiones regulares pueden llegar a ser un tema complejo, por la cantidad de opciones que ofrece. Es un tema en el que no entraremos a fondo. En general, una expresión regular en JavaScript:

  • Una expresión regular tiene la forma /.../.
  • Una expresión con la forma /.../ solo busca la primera coincidencia
  • Si se añade el modificador 'g' al final, en la forma /.../g busca todas las coincidencias
  • Si se añade el modificador 'i' al final, en la forma /.../i la búsqueda no es sensible a mayúsculas
  • Si se añade el modificador 'm' al final, en la forma /.../m, la búsqueda se hace en varias líneas (busca en toda la cadena aunque hayan carácteres de salto de línea)
  • Se pueden combinar todos estos mofificadores, por ejemplo, en la forma /.../gi.

Las expresiones regulares también se pueden utilizar con las funciones replace y search.

Un ejemplo con replace:

var myString = "Paul, Paula, Pauline, paul, Paul"; var myRegExp = /Paul/g; myString = myString.replace(myRegExp, "Ringo");

La función que se suele utilizar para buscar una cadena en un texto, es indexOf. Sin embargo, si lo que queremos es buscar con expresiones regulares, necesitamos search. Un ejemplo con search

var myString = "Paul, Paula, Pauline, paul, Paul"; var myRegExp = /Paul/; myString = myString.search(myRegExp); // devuelve igual que indexOf

Actividad 1. El usuario ha rellenado un formulario de la página. Los campos del formulario son:

  • Nombre: letras minúsculas, mayúsculas, espacios en blanco y '.'.
  • Apellidos: letras minúsculas, mayúsculas y espacios en blanco.
  • Contraseña: letras, números, y caracteres especiales. Debe tener un mínimo de 5 caracteres.
  • DNI: tiene la forma 12345678A. Puede ir desde 1 hasta 99999999Z
  • Edad: un número entre 0 y 120
  • Correo electrónico: tiene la forma cualquiercosa@dominio.dom

Supongamos que hemos recogido los valores de dicho formulario y que se han almacenado en las siguientes variables: nombre, apellidos, password, dni, edad y email.

Escribe el código JavaScript necesario para que se cumplan las restricciones indicadas anteriormente. Al final se debe mostrar por pantalla un mensaje indicando los errores encontrados si es que hay, o bien el mensaje "no hay errores" si no los hubiera.

Puedes partir del siguiente ejemplo:

<!doctype html> <html> <head> <meta charset="UTF-8"> <title>Untitled Document</title> </head> <body> <noscript> <p>Esta página requiere la activación de JavaScript.</p> </noscript> <script> var nombre="Pedr0 J."; var apellidos="Rodríguez 52"; var password="P9s5"; var dni="i23456789A"; var edad=-1; var email="pedro@rodríguez@hotmail.com"; // CÓDIGO JAVASCRIPT AQUÍ </script> </body> </html>

Entrega la actividad en un archivo llamado Act1-javascript.html

Amplicación. Manejo de fechas

El manejo de fechas puede ser algo útil en muchos casos, ya sea para escribir una fecha o bien para utilizarla al crear una cookie y poner un límite de validez.

Crear una fecha

var fecha = new Date(); // contiene la fecha actual var fecha = new Date(dateString); // dateString --> "yyyy-mm-dd hh:mm:ss" var fecha = new Date(2016,0,31,15,35,20); // Año, mes, día, hora, minutos, segundos y milisegundos.

Obtener parte de una fecha

var fecha = new Date(); var diaDelMes = fecha.getDate(); // día del mes var diaDeSemana = fecha.getDay(); // día de la semana, numéricamente hablando (domingo = 0) var mes = fecha.getMonth(); // mes, numéricamente hablando (enero = 0) var anno = fecha.getFullYear(); // año con cuatro dígitos. var milisegundos = fecha.getTime(); // número de milisegundos desde el 1/1/1970

Obtener la fecha completa

var fecha = new Date(); var fechaString = fecha.toDateString(); // Cadena de texto del tipo "Sun Feb 07 2016" var fechaUTC = fecha.toUTCString(); // Cadena de texto en formato UTC (útil para cookies)

Cambiar partes de una fecha

var nuevaFecha = new Date(); nuevaFecha.setHours(9); // 9 a.m. nuevaFecha.setMinutes(58); // 50 minutos nuevaFecha.setDate(4); // Día del mes nuevaFecha.setMonth(5); // Junio nuevaFecha.setFullYear(1999); // 1999 nuevaFecha.setTime(1454854820020); // Milisegundos desde 1/1/1970

Manipular la fecha para indique un momento futuro

En ocasiones debemos calcular la fecha que será dentro de 48 horas. Esto se puede puede calcular del siguiente modo:

  • Obtenemos los milisegundos desde 1/1/1970 hasta ahora con getTime()
  • Calculamos los milisegundos que hay en 48 horas: 1000*3600*48
  • Sumamos los milisegundos desde 1/1/1970 y los milisegundos que hay en 48 horas
  • Asignamos el número de milisegundos calculado a la fecha
fecha = new Date(); fecha.setTime(fecha.getTime() + 1000*3600*48); // 48 horas desde ahora.

Actividad 2. Escribe una página que incluya un script que muestre en pantalla la fecha en español, en el formato siguiente

15 de febrero de 2016

Entrega la actividad en un archivo llamado Act2-javascript.html

Programación avanzada

En este apartado vamos a trabajar con algunos elementos de programación sin los que resulta muy difícil programar, es decir:

Actividad 3. Copia el siguiente código fuente, y trabaja sobre él como punto de partida:

<!doctype html> <html> <head> <meta charset="UTF-8"> <title>Actividad 3</title> <script type="text/javascript"> var nombre = "Sergey Brin"; var email = "sergey.brin@gmail.com"; var password = "Gato200!"; function checkNombre(nombre){ } function checkEmail(email){ } function contarMayusculas(password){ } function contarMinusculas(password){ } function contarNumeros(password){ } function contarSimbolos(password){ } function checkPassword(password){ } function mostrarCartel(texto){ } var mensajeNombre = checkNombre(nombre); var mensajeEmail = checkEmail(email); var mensajePassword = checkPassword(password); mostrarCartel(mensajeNombre); mostrarCartel(mensajeEmail); mostrarCartel(mensajePassword); </script> </head> <body> </body> </html>

Imagina que el contenido de las variables nombre, email y password han sido leídas de los inputs de un formulario. Estas variables deben cumplir los siguientes requisitos:

  • Los nombres de personas no pueden tener números ni símbolos de puntuación (.:_-!?).
  • Las direcciones de email deben tener una única @, y el dominio debe tener la estructura siguiente: nombre.tld, como por ejemplo gmail.com
  • Las contraseñas deben tener, al menos, un número, una mayúscula, una minúscula y un símbolo especial (.:_-!?)

Escribe un código en JavaScript que haga lo siguiente:

  1. Si el nombre es válido, muestra un cartel con el texto "El nombre es válido". En caso contrario, muestra el mensaje "El nombre no es válido".
  2. Si el email es válido, muestra un cartel con el texto "El email es válido". En caso contrario, muestra el mensaje "El nombre no es válido.
  3. Muestra un cartel indicando la contraseña y el número de letras mayúsculas, minúsculas, números y símbolos especiales. Por ejemplo, la contraseña "Gato200!" mostraría el mensaje: "Gato200! -> Mayúsculas:1, Minúsculas:3, Números:3, Símbolos:1"

Para escribir el programa, debes utilizar las siguientes funciones:

  • checkNombre toma el nombre y devuelve la cadena de texto "El nombre es válido" o bien la cadena "El nombre no es válido".
  • checkEmail toma el email y devuelve la cadena de texto "El email es válido" o bien la cadena "El email no es válido"
  • checkPassword toma la contraseña y devuelve una cadena de texto con el recuento de mayúsculas, minúsculas, símbolos y números. Esta función utiliza a su vez las funciones contarMayusculas, contarMinusculas, contarNumeros y contarSimbolos para hacer el recuento.
  • contarMayusculas toma la password y devuelve el número de mayúsculas.
  • contarMinusculas toma la password y devuelve el número de minúsculas.
  • contarNumeros toma la password y devuelve el número de números.
  • contarSimbolos toma la password y devuelve el número de símbolos.
  • mostrarCartel toma un texto y lo muestra por pantalla.

Entrega la actividad en un archivo llamado Act3-javascript.html

DOM

DOM (Document Object Model) permite acceder al documento como un árbol de nodos al estilo XML. Para tratar este tema, vamos a seguir los apuntes de librosweb.es

Insertar un nuevo nodo antes, o después de otro

En ocasiones es necesario colocar un nuevo nodo exactamente delante o detrás de otro. Para estos casos, podemos utilizar el método insertBefore. Este método tiene la siguiente sintaxis:

var insertedElement = parentElement.insertBefore(newElement, referenceElement);

Como se puede ver se aplica al nodo padre del nodo de referencia y del nuevo nodo a insertar.

Añadir un nodo antes que otro

nodoReferencia.parentNode.insertBefore(nuevoNodo,nodoReferencia);

Añadir un nodo después que otro

nodoReferencia.parentNode.insertBefore(nuevoNodo,nodoReferencia.nextSibling);

Es importante tener en cuenta que nextSibling puede devolvernos algo que no esperábamos, ya que el navegador puede considerar que después de un cierto nodo, hay un nodo de texto donde nosotros no vemos nada. Si hacemos la selección correctamente (con getElementsByTagName por ejemplo) no tendremos problemas de este tipo.

Insertar un elemento al principio de una lista

var nuevoItem = document.createElement("li"); var nodoTexto = document.createTextNode("Contenido del ítem"); nuevoItem.appendChild(nodoTexto); var lista = document.getElementById("idLista"); lista.insertBefore(nuevoItem, lista.childNodes[0]);

Acceso a los atributos

Hay varias formas de crear, eliminar y acceder a los atributos

Crear un nuevo atributo

Para crear un nuevo atributo, utilizamos el método setAttribute del nodo correspondiente. Por ejemplo:

var miParrafo = document.getElementById("parrafo1"); miParrafo.setAttribute("draggable","true");

De esta forma hemos añadido al párrafo un nuevo atributo llamado draggable y cuyo valor es true

Eliminar un atributo

La forma de eliminar un atributo es similar, pero utilizando el método removeAttribute:

var miParrafo = document.getElementById("parrafo1"); miParrafo.removeAttribute("draggable");

Después de hacer esto, el atributo draggable no es hijo del nodo con id "parrafo1".

Acceder al valor de un cierto atributo

El método getAttribute nos permite obtener el valor de un cierto atributo.

var miParrafo = document.getElementById("parrafo1"); var atributoDraggable = miParrafo.getAttribute("draggable");

Acceso a todos los atributos

Otra forma de acceder a los atributos es a través del array attributes que contiene un nodo elemento. Un ejemplo:

var misH1 = document.getElementsByTagName("H1"); var eh1 = misH1[0]; for (var i=0;i<eh1.attributes.length;i++){ var attN = eh1.attributes[i].nodeName; var attV = eh1.attributes[i].nodeValue; alert("El atributo es :" + attN + " = " + attV); }

Acceso a formularios, imágenes y enlaces

Además de las cuestiones anteriores, hay una cosa más que podemos reseñar sobre el acceso a elementos del DOM. Vamos primero a observar la estructura del DOM:

Estructura del BOM (Browser Object Model)

Si observamos con atención, veremos que el objeto document tiene como hijos tres objetos:

  • forms: objeto que contiene una lista con los formularios del documento.
  • images: objeto que contiene una lista con las imágenes del documento.
  • links: objeto que contiene una lista con los enlaces del documento.

De este modo, es posible acceder directamente a la segunda imagen del siguiente modo:

var miImagen2 = document.images[1];

Del mismo modo, para acceder al primer formulario del documento:

var miFormulario1 = document.forms[0];

O bien, para acceder al primer enlace que hay en la página:

var miPrimerEnlace = document.links[0];

Actividad 4. Disponemos de la siguiente página web:

<!doctype html> <html> <head> <meta charset="UTF-8"> <title>Relato</title> <style type="text/css"> .capitulo{ font-family:sans-serif; color:red; } div.indice{ border:1px solid black; } a.entradaIndice:hover{ font-weight:bold; } </style> </head> <body> <h1 id="titulo">Dos por tres calles</h1> <h2 class="capitulo">Capítulo 1. La primera calle</h2> <p>Era una sombría noche del mes de agosto. Ella, inocente cual mariposa que surca el cielo en busca de libaciones...</p> <h2 class="capitulo">Capítulo 2. La segunda calle</h2> <p>Era una oscura noche del mes de septiembre. Ella, inocente cual abejilla que surca el viento en busca del néctar de las flores...</p> <h2 class="capitulo">Capítulo 3. La tercera calle</h2> <p>Era una densa noche del mes de diciembre. Ella, cándida cual abejilla que surca el espacio en busca de bichejos para comer...</p> </body> </html>

Utilizando únicamente JavaScript, inserta en el documento un índice que contenga un enlace a cada una de las secciones del documento. El índice debe colocarse justo después del título principal del documento (<h1>Dos por tres calles<h1>) debe quedar como se indica en el siguiente código.

<div class="indice"> <h2 style="text-align:center;">Índice del libro</h2> <ul> <li> <a href="#cap0" class="entradaIndice">Capítulo 1. La primera calle</a> </li> <li> <a href="#cap1" class="entradaIndice">Capítulo 2. La segunda calle</a> </li> <li> <a href="#cap2" class="entradaIndice">Capítulo 1. La tercera calle</a> </li> </ul> </div>

Además debes añadir (también desde JavaScript) el atributo id a cada capítulo para que el enlace correspondiente del índice tenga un anclaje donde ir.

Si se añade una nueva sección al documento, la siguiente vez que se carge la página en el navegador.

Entrega la actividad en un archivo llamado Act4-javascript.html

Para poder usar con soltura el DOM, necesitamos tener a mano su API. En https://developer.mozilla.org/es/docs/Web/API/Node podemos encontrar una referencia completa.

XPath y JavaScript

Además de utilizar los métodos comentados anteriormente para acceder al DOM, podemos utilizar expresiones XPath. Se trata de una ampliación del DOM (correspondiente al Nivel 3).

Para evaluar una expresión XPath en el DOM, debemos utiliar el método evaluate del objeto document. La definición es la siguiente:

var xpathResult = document.evaluate( xpathExpression, contextNode, namespaceResolver, resultType, result );

Donde:

  • xpathResult es un objeto de tipo XPathResult que contiene el "Node-Set" con el resultado de la evaluación.
  • xpathExpression es una cadena de texto con la expresión XPath a ser evaluada
  • contextNode especifica el nodo de contexto. Normalmente será el objeto document
  • namespaceResolver es una función que devuelve una cadena representando el espacio de nombres asociado a un cierto prefijo. En HTML el valor habitual es null, ya que no es común utilizar prefijos.
  • resultType contiene un número entero que corresponde al tipo de nodo que debe devolver la expresión XPath. Estos enteros suelen expresarse en forma de un nombre de constante de la clase XPathResult como por ejemplo XPathResult.ANY_TYPE
  • result es un objeto en el cual se almacenará el resultado. Si vale null se crea un nuevo objeto XPathResult. El valor de este parámetro coincide con el devuelto por el método evaluate y almacenado en xpathResult

La descripción de la interfaz del tipo XPathResult está especificada en https://xerces.apache.org/xerces2-j/javadocs/api/org/w3c/dom/xpath/XPathResult.html

Los tipos de nodo (resultType) son:

Result TypeValor numéricoDescripción
ANY_TYPE0Cualquier tipo de resultado de la consulta. Al no especificar claramente un tipo (de los siguientes), el resultado nunca será de tipo ANY_TYPE. El tipo de resultado dependerá del tipo de consulta realizada.
NUMBER_TYPE1Resultado numérico, como resultado de una función como count()
STRING_TYPE2Resultado que contiene una cadena de texto
BOOLEAN_TYPE3Resultado que contiene un valor booleano, resultado de funciones como not()
UNORDERED_NODE_ITERATOR_TYPE4Referencias a cualquier tipo de nodo resultado de la expresión. Los nodos no tienen por qué aparecer en el mismo orden en que aparecen en el documento.
ORDERED_NODE_ITERATOR_TYPE5Referencias a cualquier tipo de nodo resultado de la expresión. Los nodos aparecen en el mismo orden en que aparecen en el documento.
UNORDERED_NODE_SNAPSHOT_TYPE6Instantaneas de cualquier tipo de nodo resultado de la expresión. Los nodos no tienen por qué aparecer en el mismo orden en que aparecen en el documento.
ORDERED_NODE_SNAPSHOT_TYPE7Instantaneas de cualquier tipo de nodo resultado de la expresión. . Los nodos aparecen en el mismo orden en que aparecen en el documento.
ANY_UNORDERED_NODE_TYPE8Un único nodo que coincide con la expresión, no necesariamente el primero.
FIRST_ORDERED_NODE_TYPE9El primer nodo que coincide con la expresión.

Los tipos de NODE_ITERATOR contienen referencias a nodos del documento que quedan invalidados si se modifican. Tras modificar un nodo, no podremos seguir iterando sobre el resultado.

En cambio, los tipos de NODE_SNAPSHOT permiten recorrer y alterar los nodos. Sin embargo, si el docuemento cambia (por otro lado) el snapshot no contendrá la versión actual de los nodos.

La clase XPathResult posee un método llamado iterateNext utilizado habitualmente para recorrer el resultado.

Veamos un ejemplo. En este caso vamos a utilizar un iterador resultado de una consulta de tipo ANY_TYPE

<!doctype html> <html> <head> <meta charset="UTF-8"> <title>Untitled Document</title> </head> <body> <h1>Título 1</h1> <h1>Título 2</h1> <script type="text/javascript"> var titulos = document.evaluate("//h1",document, null, XPathResult.ANY_TYPE,null); var titulo = titulos.iterateNext(); // titulo contiene un nodo del DOM var cadenaTitulos = ""; while (titulo){ cadenaTitulos += titulo.textContent + "\n"; titulo = titulos.iterateNext(); } alert(cadenaTitulos); </script> </body> </html>

En el siguiente ejemplo, utilizamos un resultado de tipo SNAPSHOT. Como se puede apreciar, un snapshot no es un iterador. Por eso si tratamos de usar el método iterateNext obtendremos un error. Para acceder a un snapshot utilizamos el método snapshotItem(pos):

<!doctype html> <html> <head> <meta charset="UTF-8"> <title>Untitled Document</title> </head> <body> <div> <p>Esto es un <strong>párrafo</strong></p> <div> <h1>Título 1</h1> </div> </div> <div> <h1>Título 2</h1> </div> <script type="text/javascript"> var titulos = document.evaluate("/html/body//h1",document,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null); for (var i=0;i<titulos.snapshotLength;i++){ titulos.snapshotItem(i).innerHTML = "Localizado: " + titulos.snapshotItem(i).innerHTML; } </script> </body> </html>

La ventaja de usar este tipo de consulta es que podemos modificar el DOM sin invalidar el Node-Set de la consulta. Para comprobarlo intenta modificar los títulos en una consulta de tipo ORDERED_NODE_ITERATOR_TYPE.

La ventaja que tiene utilizar XPath, es que podemos utilizar la potencia de las expresiones de XPath. Por ejemplo, para contar el número de elementos div dentro un elemento div que hay en la siguiente página, podemos hacer lo siguiente:

<!doctype html> <html> <head> <meta charset="UTF-8"> <title>Untitled Document</title> </head> <body> <div> <div> <p>Este div está dentro de otro</p> </div> <div> <p>Este div también está dentro de otro</p> <div> <p>Este div también está dentro de otro</p> </div> </div> </div> <div> <p>Este div no está dentro de otro</p> </div> <div> <div> <p>Este div también está dentro de otro</p> </div> </div> <script type="text/javascript"> var recuentoDivInteriores = document.evaluate("count(//div/descendant::div)",document,null,XPathResult.NUMBER_TYPE,null); if (recuentoDivInteriores.resultType == XPathResult.NUMBER_TYPE){ alert("Hay " + recuentoDivInteriores.numberValue + " div dentro de otro div"); } </script> </body> </html>

Eventos

Los eventos permiten ejecutar partes de nuestro código cuando se produce un hecho concreto, como un click de ratón, o una pulsación de teclado. Para este tema en concreto vamos a seguir el tema de Libros web relativo a eventos:

Sobre el último enlace, hay que decir que se recomienda utilizar la siguiente forma para añadir un manejador de eventos:

<h1>Probando con eventos</h1> <p class="destacado">Esto es un texto</p> <img alt="Imagen ejemplo" src="imagen1.jgp"/> <script> function destacar(event){ .... ACCIONES .... } document.addEventListener("click",destacar); document.addEventListener("mouseover",destacar); document.addEventListener("mouseout",destacar); </script>

Es decir, el estándar recomendado para definir eventos es el siguiente:

  1. seleccionar el elemento: var elemento = document.getElementById("id")
  2. asociar mediante un EventListener: elemento.addEventListener("click",nombreFuncion);
  3. definir la función: function nombreFuncion(event)

Cambiar el comportamiento por defecto del código

Cuando hacemos click sobre un enlace, éste tiene un comportamiento predefindio. Sin embargo a veces podemos desear interrumpir dicho comportamiento. Ocurre lo mismo con el botón de un formulario que bajo ciertas condiciones podemos querer bloquear. Observa el siguiente código:

<!DOCTYPE html> <html lang="en"> <head> <title>Connecting Events Using HTML Attributes</title> </head> <body> <a href="somepage.html" onclick="return linkClick()">lick Me</a> <script> function linkClick() { alert("You Clicked?"); return true; } </script> </body> </html>

Y ahora vamos a probar a devolver false en linkClick

Usando la información del evento

Sobre este aspecto hay información interesante en http://librosweb.es/libro/javascript/capitulo_6/obteniendo_informacion_del_evento_objeto_event.html

Los eventos contienen ciertas propiedades que nos interesan:

  • target elemento que provocó el evento
  • type tipo de evento

Con estas dos propiedades podemos hacer gran cantidad de cosas, combinando con el DOM. Por ejemplo, observa este código: ¿Qué hace?

<!doctype html> <html> <head> </head> <body> <h1 id="titulo">Pasa el ratón por encima</h1> <p id="citas">Haz clic aquí</p> <script type="text/javascript"> var textos=["Supercalifragilísticoespialidoso", "Había una vez un circo...", "En un lugar de la Mancha"]; function textoAleatorio(event){ if (event.type == "click"){ alert("Has hecho clic"); } else if (event.type == "mouseover"){ alert("Has pasado el ratón por encima"); } var numeroAleatorio = Math.round(Math.random()*2); var elemento = event.target; elemento.innerHTML = textos[numeroAleatorio]; } var parrafo = document.getElementById("citas"); var titulo = document.getElementById("titulo"); parrafo.addEventListener("click",textoAleatorio); titulo.addEventListener("mouseover",textoAleatorio); </script> </body> </html>

El siguiente es otro ejemplo en el que se utiliza la información que pasa en el evento.

<!doctype html> <html> <head> <style type="text/css"> .muy_destacado{ color:red; font-weight:bolder; } .borde_destacado{ border:5px solid red; } </style> </head> <body> <h1>Probando con eventos</h1> <p class="destacado">Esto es un texto</p> <img alt="Imagen ejemplo" src="imagen1.jpg"/> <script> function destacar(event){ var objetoActivado = event.target; var tipoEvento = event.type; if ((objetoActivado.tagName == "P") && (tipoEvento == "click")) { if (objetoActivado.className.indexOf("muy_destacado") != -1 ){ objetoActivado.className = ""; } else { objetoActivado.className = "muy_destacado borde_destacado"; } } else if (objetoActivado.tagName == "H1"){ if (tipoEvento == "mouseover"){ objetoActivado.className = "muy_destacado"; } if (tipoEvento == "mouseout"){ objetoActivado.className = ""; } } else if ((objetoActivado.tagName == "IMG") && (tipoEvento == "click")){ objetoActivado.className = "borde_destacado"; } } document.addEventListener("click",destacar); document.addEventListener("mouseover",destacar); document.addEventListener("mouseout",destacar); </script> </body> </html>

En el ejemplo anterior, podemos ver como se ha asociado el manejador destacar a todos los elementos del documento.

Actividad 5. Escribe una página que muestre una imagen. Al hacer clic sobre la imagen, muestra la siguiente de un total de 5. Al llegar a la última, vuelve a mostrar la primera.

Entrega la actividad en un archivo llamado Act5-javascript.html

Varios manejadores en serie

Se pueden definir varios manejadores para un mismo elemento, de forma que se ejecutarán uno a uno. Por ejemplo, observa el siguiente código

<!doctype html> <html> <head> </head> <body> <h1 id="titulo">Haz clic en la imagen</h1> <img id="imagen" src="imagen1.jpg"/> <script type="text/javascript"> var imagenes = ['imagen1.jpg', 'imagen2.jpg', 'imagen3.jpg', 'imagen4.jpg']; var colores = ['red', 'green', 'blue', 'yellow']; function imagenAleatoria(event){ var numeroAleatorio = Math.round(Math.random()*3); var imagen = event.target; imagen.src = imagenes[numeroAleatorio]; } function bordeAleatorio(event){ var numeroAleatorio = Math.round(Math.random()*3); var imagen = event.target; imagen.style.width = "300px"; var border = "10px solid "+colores[numeroAleatorio]; imagen.style.border=border; } var imagen = document.getElementById("imagen"); imagen.addEventListener("click",imagenAleatoria); imagen.addEventListener("click",bordeAleatorio); </script> </body> </html>

Eliminar manejadores

Imaginemos que queremos que un cierto evento se gestione bajo una cierta condición, pero que bajo otra condición diferente ya no se debe seguir gestionando. El ejemplo siguiente se realiza un cambio de imagen y de borde al hacer clic sobre la imagen, hasta que se hace clic sobre un botón que detiene este comportamiento:

<!doctype html> <html> <head> </head> <body> <h1 id="titulo">Haz clic en la imagen</h1> <img id="imagen" src="imagen1.jpg"/> <button id="boton">No cambiar la imagen</button> <script type="text/javascript"> var imagenes = ['imagen1.jpg', 'imagen2.jpg', 'imagen3.jpg', 'imagen4.jpg']; var colores = ['red', 'green', 'blue', 'yellow']; var imagen = document.getElementById("imagen"); function imagenAleatoria(event){ var numeroAleatorio = Math.round(Math.random()*3); imagen.src = imagenes[numeroAleatorio]; } function bordeAleatorio(event){ var numeroAleatorio = Math.round(Math.random()*3); imagen.style.width = "300px"; var border = "10px solid "+colores[numeroAleatorio]; imagen.style.border=border; } function detenerImagenAleatoria(event){ imagen.removeEventListener("click",imagenAleatoria); } imagen.addEventListener("click",imagenAleatoria); imagen.addEventListener("click",bordeAleatorio); var boton = document.getElementById("boton"); boton.addEventListener("click",detenerImagenAleatoria); </script> </body> </html>

Como se puede ver, para éliminar los manejadores se utiliza el método removeEventListener. Al hacer clic en el botón, se elimina el gestor de eventos imagenAleatoria, pero no se elimina bordeAleatorio, de modo que seguirá cambiando el borde, pero no la imagen.

Arrastrar y soltar

A partir de Internet Explorer 10, así como en Chrome, Firefox, Opera y Safari, se soportan diferentes eventos de arrastrar y soltar. Los eventos soportados son:

  • dragstart evento lanzado por el elemento arrastrado, al inicio operación de arrastre en el navegador (no desde el sistema de archivos)
  • dragenterevento lanzado por el elemento invadido, en la entrada en su espacio
  • dragover evento lanzado por el elemento invadido, mientras el ratón se mueve sobre el elemento
  • dragleave evento lanzado por elemento invadido al dejar de serlo
  • drag evento lanzado por el elemento arrastrado continuamente durante la operación de arrastre
  • drop evento lanzado por el objeto sobre el que se suelta el objeto arrastrado
  • dragend Recibido por el objeto arrastrado al finalziar el arrastre (no desde el sistema de archivos)

Para que un evento sea arrastrable, hay que añadirle el atributo draggable="true". Por ejemplo, un elemento arrastrable se declarará así

<div draggable="true" class="box red">

El siguiente ejemplo hace un seguimiento de los eventos lanzados durante una operación de arrastre:

<!doctype html> <html> <head> <style type="text/css"> #dropZone{ width:120px; height:120px; background-color:blue; text-align:center; } #box{ width:100px; height:100px; background-color:red; text-align:center; } </style> </head> <body> <div id="box" draggable="true">Arrastrar</div> <div id="dropZone">DropZone</div> <script> var box = document.getElementById("box"); var dropZone = document.getElementById("dropZone"); function dragLog(e){ console.log(e.type + " -> " + this.id); } function dropLog(e){ console.log(e.type + " -> " + this.id); e.preventDefault(); } box.addEventListener("dragstart",dragLog); box.addEventListener("dragenter",dragLog); box.addEventListener("dragover",dragLog); box.addEventListener("dragleave",dragLog); box.addEventListener("drag",dragLog); box.addEventListener("dragend",dragLog); dropZone.addEventListener("dragstart",dragLog); dropZone.addEventListener("dragenter",dragLog); dropZone.addEventListener("dragleave",dragLog); dropZone.addEventListener("drag",dragLog); dropZone.addEventListener("dragend",dragLog); dropZone.addEventListener("dragover",dragLog); dropZone.addEventListener("drop",dropLog); </script> </body> </html>

Como se puede ver, se pueden destacar los siguientes hechos:

  1. Al empezar el arrastre de box, recibe el evento dragstart
  2. Mientras estamos arrastrando box, se recibe continuamente el evento drag
  3. Mientras box no está sobre ningún otro objeto, box recibe continuamente el evento dragover
  4. Al entrar en dropZone, se recibe un evento dragover
  5. Mientras estamos arrastrando sobre dropZone, recibe continuamente el evento dragover
  6. Cuando soltamos box sobre dropZone, éste recibe un evento dragleave
  7. Cuando soltamos box, recibe un evento dragend
  8. Los eventos dragover y drop son gestionados por el mismo manejador, que incluye la llamada al método preventDefault del evento. De no hacerlo así, no podremos recoger el evento drop

Ateniendo al ejemplo anterior, podemos crear un código que detecte cuándo se está moviendo box sobre dropZone y cuándo se ha soltado.

<!doctype html> <html> <head> <style type="text/css"> #dropZone{ width:120px; height:120px; background-color:blue; text-align:center; } #box{ width:100px; height:100px; background-color:red; text-align:center; } #dropStatus{ background-color:yellow; } .invadido{ border:2px dashed yellow; } </style> </head> <body> <div id="box" draggable="true">Arrastrar</div> <div id="dropZone">DropZone</div> <div id="dropStatus"></div> <script> var box = document.getElementById("box"); var dropZone = document.getElementById("dropZone"); var dropStatus = document.getElementById("dropStatus"); function limpiarStatus(){ dropStatus.innerHTML=""; } function dropLog(e){ e.preventDefault(); console.log(e.type + " -> " + this.id); if (e.type == "dragenter"){ dropStatus.innerHTML ="Has entrado en dropzone"; dropZone.className = "invadido"; } else{ dropStatus.innerHTML ="Has soltado box"; dropZone.className = ""; } } box.addEventListener("dragstart",limpiarStatus); dropZone.addEventListener("dragenter",dropLog); dropZone.addEventListener("dragleave",dropLog); </script> </body> </html>

Como se puede comprobar en el ejemplo anterior, al gestionar el evento dragleave solamente comprobamos que box ha abandonado la zona dropzone. Pero esto no es del todo útil. Lo interesante es poder transferir información y de objetos entre zonas de descarga.

Transferencia de datos

La transferencia de datos se puede conseguir asociando a un evento la información de interés con el método setData. Por ejemplo, supongamos que una caja empieza a ser arrastrada. Entonces podemos asociar a su evento de arrastre cierta información del siguiente modo:

function manejadorDragStart(e){ e.dataTransfer.setData("nombreCampo","valor"); }

Más adelante podemos recuperar dicha información con el método getData del siguiente modo

[...] var informacionTransferida = e.dataTransfer.getData("nombreCampo"); [...]

Transferencia de objetos entre zonas de arrastre

Empleando la transferencia de información, podemos transferir elementos junto con información entre diferentes zonas de descarga. Los aspectos a tener en cuenta son:

Contar con más de una zona de descarga. Por ejemplo:

Si cada zona de descarga representa algo, por ejemplo, diferentes estados de un elemento, debemos utilizar más de una zona de descarga.

<div id="zona-drop1" class="zona-drop"> <div id="box1" draggable="true" class="box navy"></div> <div id="box2" draggable="true" class="box red"></div> </div> <div id="zona-drop2" class="zona-drop"></div>

Tener localizados todos los elementos arrastrables

Todos los elementos arrastrables tiene el atributo draggable. Podemos utilizar este hecho del siguiente modo:

var arrastrables = document.querySelectorAll("[draggable]"); var zonasdrop = document.querySelectorAll(".zona-drop");

De esta forma, guardamos en el array arrastrables todos los elementos arrastrables. Del mismo modo, las zonas de descarga quedan almacenadas en zonasdrop gracias a que son de clase zona-drop

Añadir al evento de arrastre el id del objeto arrastrado

Algunos eventos de arrastre son recogidos por el elemento arrastrado y otros por el elemento de descarga. El problema es que desde cada uno de estos tipos de eventos no es posible conocer la indentidad del otro. Para poder identificar el elemento que está siendo arrastrado cuando estamos gestionando un evento recogido por el elemento de descarga, debemos añadir en el elemento de arrastre la identidad del elemento arrastrado. Esto lo podemos hacer al recibir el elemento dragstart (asociado al elemento arrastrado):

for (var i = 0; i < arrastrables.length; i++) { arrastrables[i].addEventListener("dragstart", function(e) { // Guardamos en el evento el id del arrastrado e.dataTransfer.setData("text",this.id); }); }

Asociar dragenter, dragleave, dragover y drop al elemento de descarga

Debemos asignar el comportamiento deseado a los elementos de descarga (contenedores), y recoger la identidad del elemento arrastrado:

for (i = 0; i < zonasdrop.length; i++) { zonasdrop[i].addEventListener("dragenter", function(e) { // this --> contenedor // cambiar estilo del contenedor (añadir clase .drag-enter) this.className += " drag-enter"; }); zonasdrop[i].addEventListener("dragleave", function(e) { // this --> contenedor // cambiar estilo del contenedor (dejar como al principio) this.className = "zona-drop"; }); zonasdrop[i].addEventListener("dragover", function(e) { // this --> contenedor // evitar el comportamiento por defecto del navegador e.preventDefault(); // el evento no continúa su comportamiento predefinido en el navegador return false; }); zonasdrop[i].addEventListener("drop", function(e){ // this --> contenedor // evitar el comportamiento por defecto del navegador e.preventDefault(); e.stopPropagation(); // cambiar estilo del contenedor (dejar como al principio) this.className = "zona-drop"; // encontrar el elemento arrastrado var arrastradoId = e.dataTransfer.getData("text"); var arrastrado = document.getElementById(arrastradoId); // el nodo arrastrado pasa a ser hijo de "this". this.appendChild(arrastrado); // el evento no continúa su comportamiento predefinido en el navegador return false; }) }

El código completo

<!DOCTYPE html> <html lang="en"> <head> <title>Chapter 10: Example 21</title> <style> [zona-descarga] { height: 400px; width: 200px; margin: 2px; background-color: gainsboro; float: left; } .drag-enter { border: 2px dashed #000; } .box { width: 200px; height: 200px; } .navy { background-color: navy; } .red { background-color: red; } </style> </head> <body> <div zona-descarga="true"> <div id="box1" draggable="true" class="box navy"></div> <div id="box2" draggable="true" class="box red"></div> </div> <div zona-descarga="true"></div> <script> function handleDragStart(e) { e.dataTransfer.setData("text", this.id); } function handleDragEnterLeave(e) { if (e.type == "dragenter") { this.className = "drag-enter"; } else { this.className = ""; } } function handleOverDrop(e) { e.preventDefault(); var draggedId = e.dataTransfer.getData("text"); var draggedEl = document.getElementById(draggedId); if (e.type == "drop"){ this.appendChild(draggedEl); this.className = ""; } } var draggable = document.querySelectorAll("[draggable]"); var targets = document.querySelectorAll("[zona-descarga]"); for (var i = 0; i < draggable.length; i++) { draggable[i].addEventListener("dragstart", handleDragStart); } for (i = 0; i < targets.length; i++) { targets[i].addEventListener("dragover", handleOverDrop); targets[i].addEventListener("drop", handleOverDrop); targets[i].addEventListener("dragenter", handleDragEnterLeave); targets[i].addEventListener("dragleave", handleDragEnterLeave); } </script> </body> </html>

Actividad 6. Observa la aplicación Trello. En Trello, se pueden crear tarjetas que representan una tarea. Cada tarea está en un contenedor que respresenta el estado de la tarea. Los estados pueden ser cosas como "Por hacer", "En progreso" o "Completado".

Escribe una página en JavaScript que contenga tres contenedores, con los encabezados "TODO", "DOING" y "DONE". En el contenedor "TODO" habrán tres tarjetas que contienen el texto "Tarea1", "Tarea2" y "Tarea3". Las tarjetas deben poderse mover de un contenedor a otro.

Además, en la parte inferior de la página, debe aparecer una lista con el contenido de cada contenedor, que cambiará cada vez que se haga un cambio de tarjeta.

Entrega la actividad en un archivo llamado Act6-javascript.html

Almacenamiento de datos

JavaScript permite almacenar datos en el lado del cliente de varias formas:

  • Cookies
  • Almacenamiento local
  • Almacenamiento de sesión

Almacenamiento de datos mediante cookies

Las cookies, qué son y qué información almacenan queda descrito en http://cybmeta.com/que-son-las-cookies-y-como-funcionan/

Para saber cómo utilizar las cookies podemos consultar en http://cybmeta.com/cookies-en-javascript/

Almacenamiento local

Las cookies tienen algunas inconvenientes:

  • Complejas de manejar
  • Carga innecesaria para las cabeceras HTTP
  • Hay un máximo de cookies

HTML5 introdujo web storage:

  • session storage se borra al cerrar el navegador
  • local storage persiste entre visitas

Al igual que las cookies, web storage está asociado a cada dominio, y no se puede acceder a los ítems de otros dominios.

Definir un nuevo ítem

Se puede definir un nuevo item de dos maneras distintas:

localStorage.setItem("userName","Pedro"); localStorage.userName = "Pedro";

Para modificar un elemento, basta con volver a insertar un ítem con el mismo nombre.

Obtener el valor de un ítem

La extracción del valor es similar a la inserción

var name = localStorage.getItem("userName"); var name = localStorage.userName;

Borrar un ítem

localStorage.removeItem("userName"); localStorage.userName = null;

Borrar todos los ítems

Para borrar todos los ítems, usamos el método clear:

localStorage.clear();

Almacenamiento de sesión

El almacenamiento de sesión es similar al almacenamiento local, con dos matices:

  • En lugar de localStorage se utiliza el término sessionStorage
  • Al cerrar el navegador, los ítems de sesión son eliminados.