materials

APIs HTML5: Drag And Drop. Local Storage. Geolocalización. API de Google Maps

Introducción

En este tema varemos diferentes APIs incluidas en HTML5 (como la de Local Storage) y otras que se han hecho muy populares como la de Google Maps.

HTML5 incluye un buen número de APIs que facilitan el trabajo con cosas complejas, como

Aquí comentaremos Storage, Drag&Drop, Geolocation, File Access, Communication, Web Workers, History y Offline

HTML Drag And Drop API

Con HTML5 es muy sencillo arrastrar y soltar elementos en una página web. Podemos arrastrar y soltar cualquier nodo DOM (una imagen, un archivo, enlaces, texto seleccionado, …). Para ello sólo es necesario que ese elemento tenga el atributo dragable="true". Si le ponemos false no se podrá arrastrar y si no definimos el atributo podrá o no arrastrarse según el valor predeterminado del navegador (en la mayoría son dragables las imágenes, los links y las selecciones de texto).

Al arrastrar y soltar intervienen 2 elementos diferentes:

Para poder realizar la operación event tiene una propiedad llamada dataTransfer que es un objeto en el que almacenamos qué elemento estamos arrastrando (o cualquier otra cosa que queramos) y así cuando se suelte sobre el elemento destino éste último pueda saber quién se le ha soltado.

Los pasos para arrastrar y soltar un elemento son:

  1. El elemento debe ser draggable
  2. Capturamos el evento dragstart. Este evento se produce sobre un elemento cuando comenzamos a arrastrarlo. Deberemos almacenar en el dataTransfer quién está siendo arrastrado (si no guardamos nada se guarda automáticamente su src si es una imagen o su href si es un enlace). Indicaremos el tipo del dato que estamos almacenando (texto plano, HTML, fichero, etc) y su valor. Ej.:
  <img id="imgGoogle" src="https://upload.wikimedia.org/wikipedia/commons/thumb/5/51/Google.png/320px-Google.png">
  <div id="zonaDrop1" class="drop">
    <p>Puedes soltar aquí la imagen</p>
  </div>
  <div id="zonaDrop2" class="drop">
    <p>Y también aquí</p>
  </div>
  document.getElementById('imgGoogle').addEventListener('dragstart', (event) => {
      event.dataTransfer.setData('text/plain', event.target.id);  // Estamos guardando el texto 'imgGoogle'
  })
  1. Capturamos el evento dragover. Este evento se produce cada pocas décimas de segundo sobre elemento sobre el que se está arrastrando algo. Por defecto no se puede soltar un elemento en ningún sitio así que capturamos este evento para evitar que el navegador haga la acción por defecto e impida que se suelte lo que estamos arrastrando. Ej.:
    document.getElementById('zonaDrop1').addEventListener('dragover', (event) => {
     event.preventDefault();
    })
    document.getElementById('zonaDrop2').addEventListener('dragover', (event) => {
     event.preventDefault();
    })
    
  2. Capturamos el evento drop. Este evento se produce sobre elemento sobre el que se suelta lo que estábamos arrastrando. Lo que haremos es evitar el comportamiento por defecto del navegador (que en caso de imágenes o enlaces es cargarlos en la página), obtener quién se ha soltado a partir del objeto dataTransfer y realizar lo que queramos, que normalmente será añadir el objeto arrastrado como hijo del objeto sobre el que se ha hecho el drop. Ej.:
    document.getElementById('zonaDrop1').addEventListener('drop', (event) => {
     event.preventDefault();
     const data=event.dataTransfer.getData("text/plain");      // Obtenemos ìmgGoogle'
     event.target.appendChild(document.getElementById(data));
    })
    document.getElementById('zonaDrop2').addEventListener('drop', (event) => {
     event.preventDefault();
     const data=event.dataTransfer.getData("text/plain");      // Obtenemos ìmgGoogle'
     event.target.appendChild(document.getElementById(data));
    })
    

Podéis ver el funcionamiento de este ejemplo:

NOTA: si hacemos draggable un elemento, por ejemplo un párrafo, ya no se puede seleccionar con el ratón ya que al pinchar y arrastrar se mueve, no se selecciona. Para poder seleccionarlo debemos pinchar y arrastrar el ratón con las teclas Ctrl+Alt pulsadas o hacerlo con el teclado. Ejemplo:

Podemos obtener más información de esta API MDN web docs.

EJERCICIO: mira y modifica el ejemplo de w3schools para entender bien el funcionamiento del Drag&Drop (ten en cuenta que en vez de .addEventListener() las llamadas a los escuchadores están puestas como atributos del HTML pero el funcionamiento es el mismo).

Almacenamiento en el cliente: API Storage

Antes de HTML5 la única manera que tenían los programadores de guardar algo en el navegador del cliente (como sus preferencias, su idioma predeterminado para nuestra web, etc) era utilizando cookies. Las cookies tienen muchas limitaciones y es engorroso trabajar con ellas.

HTML5 incorpora la API de Storage para subsanar esto. Además existen otros métodos de almacenamiento en el cliente más avanzados como IndexedDB (es un estándar del W3C pero aún con poco soporte entre los navegadores).

El funcionamiento de la API Storage es muy sencillo: dentro del objeto window tendremos los objetos localStorage y sessionStorage donde podremos almacenar información en el espacio de almacenamiento local (5 o 10 MB por sitio web según el navegador, que es mucho más de lo que teníamos con las cookies). La principal diferencia entre ellos es que la información almacenada en localStorage nunca expira, permanece allí hasta que la borremos (nosotros o el usuario) mientras que la almacenada en sessionStorage se elimina automáticamente al cerrar la sesión el usuario.

Sólo los navegadores muy antiguos (Internet Explorer 7 y anteriores) no soportan esta característica. Puedo saber si el navegador soporta o no esta API simplemente mirando su typeof:

if (typeof(Storage) === 'undefined')    // NO está soportado

Tanto localStorage como sessionStorage son como un objeto global al que tengo acceso desde el código. Lo que puedo hacer con ellos es:

Sólo podemos guardar objetos primitivos (cadenas, números, …) por lo que si queremos guardar un objeto o un array hay que convertirlo a una cadena JSON con localStorage.setItem('dato', JSON.stringify('objeto')). Para recuperar el objeto haremos let miObjeto = JSON.parse(localStorage.getItem('dato')).

Cada vez que cambia la información que tenemos en nuestro localStorage se produce un evento storage. Si, por ejemplo, queremos que una ventana actualice su información si otra cambia algún dato del storage haremos:

window.addEventListener("storage", actualizaDatos);

y la función ‘actualizaDatos’ podrá leer de nuevo lo que hay y actuar en consecuencia.

EJERCICIO: comprueba qué tienes almacenado en el localStorage y el sessionStorage de tu navegador. Guarda y recupera algunas variables. Luego cierra el navegador y vuelve a abrir la página. ¿Están las variables guardadas en localStorage? ¿Y las de sessionStorage?

Puedes ver un ejemplo en este vídeo de cómo almacenar en el Storage datos del usuario.

A tener en cuenta

localStorage, sessionStorage y cookies almacenan información en un navegador específico del cliente, y por tanto:

Podríamos usar localStorage para almacenar localmente los datos con los que trabaja una aplicación web. Así conseguiríamos minimizan los accesos al servidor y que la velocidad de la aplicación sea mucho mayor al trabajar con datos locales. Pero periódicamente debemos sincronizar la información con el servidor.

Storage vs cookies

Ventajas de localStorage:

Ventajas de las cookies:

Cookies

Son pequeños ficheros de texto y tienen las siguientes limitaciones:

Cada cookie almacena los siguientes datos:

Un ejemplo de cookie sería:

username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 UTC;

Se puede acceder a las cookies desde document.cookie que es una cadena con las cookies de nuestras páginas. Para trabajar con ellas conviene que creemos funciones para guardar, leer o borrar cookies, por ejemplo:

function setCookie(cname, cvalue, cexpires, cpath, cdomain, csecure) {
  document.cookie = cname + '=' + cvalue + 
    (cexpires?';expires='+cexpires.toUTCString():'') + 
    (cpath?';path='+cpath:'') + 
    (cdomain?';domain='+cdomain:'') + 
    (csecure?';secure':'')
}
function getCookie(cname) {
    if(document.cookie.length > 0){
        start = document.cookie.indexOf(cname + '=')
        if (start != -1) {   // Existe la cookie, busquemos dónde acaba su valor
            //El inicio de la cookie, el nombre de la cookie mas les simbolo '='
            start = start + nombre.length + 1
            //Buscamos el final de la cookie (es el simbolo ';')
            end = document.cookie.indexOf(';', start + cname.length + 1)
            if (end === -1) {   // si no encuentra el ; es que es la última cookie
                end = document.cookie.length;
            }
            return document.cookie.substring(start + cname.length + 1, end)
        }
    }
    return ''   // Si estamos aquí es que no hemos encontrado la cookie
}
function delCookie(cname) {
    return document.cookie = cname + '=;expires=Thu, 01 Jan 1970 00:00:01 GMT;'
}

Podéis ver en este vídeo un ejemplo de cómo trabajar con cookies, aunque como ya hemos dicho lo recomendable es trabajar con Storage.

Geolocation API

Esta API permite a la aplicación web acceder a la localización del usuario si éste da su permiso. Muchos navegadores sólo permiten usarlo en páginas seguras (https).

Podemos acceder a esta API mediante el objeto geolocation de navigator. Para saber si nuestro navegador soporta o no la API podemos hacer:

if (geolocation in navigator)   // devuelve true si está soportado

Para obtener la posición este objeto proporciona el método navigator.geolocation.getCurrentPosition() que hace una petición asíncrona. Cuando se reciba la posición se ejecutará la función callback que pasemos como parámetro y que recibirá las coordenadas de la localización. Podemos pasar otra como segundo parámetro que se ejecutará si se produce algú error y que recibirá un objeto con la propiedad code que indica el error producido. Ej.:

navigator.geolocation.getCurrentPosition(
  (position) => {
    pinta_posicion(position.coords.latitude, position.coords.longitude)
  },
  (error) => {
    switch(error.code) {
      case error.PERMISSION_DENIED: // El usuario no autoriza al navegador a acceder a la localización
        msg = 'El usuario ha denegado la petición de geolocalización'
        break
      case error.POSITION_UNAVAILABLE: // No se puede obtener la localización
        msg = 'La información de localización no está disponible.'
        break
      case error.TIMEOUT: // Ha expirado el tiempo para obtener la localización
        msg = 'Ha expirado el tiempo para obtener la localización'
        break
      case error.UNKNOWN_ERROR:
        msg = 'Se ha producido un error desconocido.'
        break
    }
    muestra_error(msg)
  }
)

Si queremos ir obteniendo continuamente la posición podemos usar el método .watchPosition() que tiene los mismos parámetros y funciona igual pero se ejecuta repetidamente. Este método devuelve un identificador para que lo podemos detener cuando queremos con .clearWatch(ident). Ej.:

const watchIdent = navigator.geolocation.watchPosition(
  (position) => pinta_posicion(position.coords.latitude, position.coords.longitude),
  (error) => muestra_error(error)
)
...
// Cuando queremos dejar de obtener la posición haremos
navigator.geolocation.clearWatch(watchIdent)

Las principales propiedades del objeto de localización (algunas sólo estarán disponible cuando usemos un GPS) son:

Podemos pasarle como tercer parámetro al método getCurrentPosition un objeto JSON con una o más de estas propiedades:

Podemos obtener más información de esta API en MDN web docs y ver y modificar ejemplos en w3schools y muchas otras páginas. i

Google Maps API

Para poder utilizar la API en primer lugar debemos obtener una API KEY de Google.

Una vez hecho para incluir un mapa en nuestra web debemos cargar la librería para lo que incluiremos en nuestro código el script:

<script async defer
  src="https://maps.googleapis.com/maps/api/js?key=ESCRIBE_AQUI_TU_API_KEY&callback=initMap">
</script>

(el parámetro callback será el encargado de llamar a la función initMap() que inicie el mapa)

Ahora incluir un mapa es tán sencillo como crear un nuevo objeto de tipo Map que recibe el elemento DOM donde se pintará (un div normalmente) y un objeto con los parámetros del mapa (como mínimo su centro y el zoom):

let map
function initMap() {
  map = new google.maps.Map(document.getElementById('map'), {
    center: { lat: 38.6909085, lng: -0.4963000000000193 },
    zoom: 12
  })
}

Por su parte añadir un marcador es igual de simple. Creamos una instancia de la clase Marker a la que le pasamos al menos la posición, el mapa en que se creará y un título para el marcador:

let marker = new google.maps.Marker({
  position: { lat: 38.6909085, lng: -0.4963000000000193 },
  map: map,
  title: 'CIP FP Batoi'
})

Aquí tenéis el ejemplo anterior:

Podemos obtener más información de esta API en Google Maps Plataform, en el tutorial de w3schools y en muchas otras páginas.