portada proyecto meteoApp

Proyecto MeteoApp – Parte #2

Última actualización:

Retomamos nuestro proyecto MeteoApp donde lo dejamos. Si acabas de aterrizar aquí, puedes ver antes la parte #1.

Primeras interacciones entre nuestra UI y la API de AccuWeather

Recordemos que en el archivo forecast.js tenemos nuestras dos funciones que trabajan como un equipo (getCity getWeather). Vamos a comentar la parte donde las invocamos, porque esto al fin y al cabo era un test para ver en la consola si todo funcionaba correctamente.

// getCity('zurich')
//     .then(data => {
//         return getWeather(data.Key);
//     }).then(data => {
//         console.log(data);
//     })
//     .catch(err => console.log(err));

Nuestro objetivo es utilizar el campo de búsqueda para obtener el valor que el usuario inserte (una ciudad) y hacer la request con ese valor. Como ya adelantamos, todo lo relacionado con manipulación del DOM lo haremos en el archivo app.js ¡Vamos a ello! 👩‍💻

1. Obtenemos una referencia del formulario (<form>) y la llamamos cityForm. Le adjuntamos un event listener vinculado a un submit event. Porque cuando el usuario le de a la tecla enter, estará haciendo un envío (submit) del formulario. ⌨

Como segundo parámetro le pasamos una callback function que recibe el event object como argumento. Recuerda que el event object nos proporciona cierta información del evento al que se lo vincule (al evento submit en este caso). 

2. Evitamos que la página se recargue usando el método preventDefault().

3. Obtenemos el value de lo que el usuario introduzca. Como el usuario busca una ciudad, ese será el nombre de nuestra variable donde guardaremos el value. Podemos obtener el value a través del la propiedad name del formulario. 

Nos aseguramos de que no haya espacios en blanco usando el método trim() sobre la ciudad. Una vez tenemos el value del usuario, podemos borrar el contenido del campo cuando el usuario pulse enter. 

const cityForm = document.querySelector('form');

cityForm.addEventListener('submit', e => {
    // prevent page refresh
    e.preventDefault()

    // get input value (city)
    const city = cityForm.city.value.trim();
    cityForm.reset();
});

Dentro de la callback function también vamos a actualizar la UI, pero lo vamos a hacer en una función independiente que luego invocaremos dentro de dicha callback function. 

4. Creamos esa función independiente a continuación de la const cityForm, y la llamamos updateCity. Será una función asíncrona, porque dentro de ella llamaremos a getCity getWeather, y por tanto, tardará cierto tiempo en completarse. ⌛

La función updateCity recibirá una ciudad como parámetro.

const updateCity = async (city) => {

};

5. Así que invocamos a updateCity dentro de la callback function de cityForm y le pasamos la ciudad que ha buscado el usuario.

cityForm.addEventListener('submit', e => {
    // prevent page refresh
    e.preventDefault()
    // get input value (city)
    const city = cityForm.city.value.trim();
    cityForm.reset();
    // update UI with new city
    updateCity(city);
});

6. Hacemos un console.log de la ciudad en updateCity para ver si todo marcha bien. Si así es, deberíamos ver en la consola el nombre que escribamos en el campo de búsqueda. Puedes deshacerte del console.log una vez lo compruebes. 

Con esto funcionando, ya podemos llamar a las funciones del archivo forecast.js. 😮

7. Creamos una variable llamada cityDetails y almacenamos ahí la función getCity, a la que le pasamos por parámetro la ciudad que escriba el usuario. Usamos la keyword await para esperar a que se resuelva la promesa antes de asignarle el valor de cityDetails a dicha variable.

De esta manera conseguimos esperar hasta obtener el valor de cityDetails, y cuando eso ocurra, ya podemos llamar a getWeather. 

8. Creamos otra variable a continuación llamada weather, que almacena la función getWeather. A esa función le pasamos el key code de la ciudad, que recordemos, se encuentra en la propiedad Key.

const updateCity = async (city) => {
    // console.log(city);
    const cityDetails = await getCity(city);
    const weather = await getWeather(cityDetails.Key);
};

🧐 Ten en cuenta que weather nos devolvería un objeto, como hemos comprobado antes en la consola. (Un array con un objeto como único elemento del array).

9. Por esta razón, hacemos un return de un objeto (en lugar del console.log). Este objeto va a tener dos propiedades:

  • cityDetails
  • weather

cuyos valores serán equivalentes a los nombres de las propiedades.

    return {
        cityDetails: cityDetails,
        weather: weather
    }

10. Sabiendo que la función updateCity devuelve una promesa, ya podemos configurar qué sucede si la promesa resuelve (usando el método then(). Recuerda que desde la callback function del then() tenemos acceso a la información del return de arriba.

Hacemos únicamente un console.log de los datos y le añadimos también un método catch() para capturar el error que suceda en caso de que la promesa sea rechazada.

    // update UI with new city
    updateCity(city)
        .then(data => console.log(data))
        .catch(err => console.log(err));

Si ahora escribimos una ciudad (Basel, por ejemplo) en el campo de búsqueda y le damos a enter, en nuestra consola nos debería aparecer un objeto con el nombre de esa ciudad y sus condiciones meteorológicas. 

  • 🔻{cityDetails: {…}, weather: {…}}
    • ▷ cityDetails: {Version: 1, Key: "312066", Type: "City", Rank: 41, LocalizedName: "Basel", …}
    • ▷ weather: {LocalObservationDateTime: "2019-10-29T14:57:00+01:00", EpochTime: 1572357420, WeatherText: "Cloudy", WeatherIcon: 7, HasPrecipitation: false, …}
    • ▷ __proto__: Object

Utilizando el object shorthand notation

En nuestro código, cuando hacemos un return del objeto que va a albergar la ciudad y sus condiciones meteorológicas, usamos esta sintaxis:

    return {
        cityDetails: cityDetails,
        weather: weather
    }

JS añadió recientemente la posibilidad de simplificar nuestro código en estos casos (casos en los que el nombre de la propiedad es el mismo que el nombre del valor). Así, lo que podemos hacer es librarnos de una de las repeticiones.

JS interpretará entonces que hemos querido darle el mismo nombre a la propiedad del objeto que a su valor.

    return { cityDetails, weather }

Mostrando el contenido en nuestra UI

Vamos por fin a utilizar la ciudad que el usuario busque y mostrársela en pantalla. Lo primero que debemos hacer es obtener algunas referencias de algunos elementos HTML para manipularnos en el DOM. 

1. Obtenemos una referencia de la card y otra de los details.


        <div class="card shadow rounded">
            <img src="https://via.placeholder.com/400x300" class="time card-img-top">
            <div class="icon bg-light mx-auto text-center">
                <!-- icon will go here -->
            </div>
            <div class="text-muted text-uppercase text-center details">
                <h5 class="my-3">City name</h5>
                <div class="my-3">Weather condition</div>
                <div class="display-4 my-4">
                    <span>temp</span>
                    <span>&deg;C</span>
                </div>
            </div>
        </div>
const card = document.querySelector('.card');
const details = document.querySelector('.details');

2. Creamos una función llamada updateUI. Ésta será responsable de recibir información y mostrarla en la pantalla (la UI). Esa información ya la especificamos en el return de la función updateCity, y la obtenemos en el método then() de dicha función.

const updateUI = (data) => {

};

Así que en el método then(), en lugar de hacer un console.log, invocamos a la función updateUI y le pasamos la data. De esta manera, la función updateUI mostrará esa data en nuestra pantalla. 

    updateCity(city)
        .then(data => updateUI(data))
        .catch(err => console.log(err));

No guardes todavía porque si lo haces, ya no verás los datos en la consola, y los vamos a necesitar luego. O simplemente comenta el código anterior (el console.log) para poder usarlo si lo necesitas.

3. De vuelta al contenido de la función updateUI, creamos una variable llamada cityDetails y le damos el valor de cityDetails, al cual tenemos acceso a través del parámetro data. Esto lo hacemos porque así es más cómodo usar esa primera propiedad de nuestro objeto cuando la necesitemos más adelante.

Hacemos lo mismo con la segunda propiedad de nuestro objeto (weather). Llamamos a la variable weather, para ser consistentes.

const updateUI = (data) => {

    const cityDetails = data.cityDetails;
    const weather = data.weather;
};

4. Hecho esto, vamos a mostrar el contenido en nuestra web. 💪 Para ello, debemos crear una HTML template. Si nos fijamos en el código HTML que engloba los details, vemos que tenemos el nombre de la ciudad, la condición meteorológica y la temperatura.

Sabiendo esto, podemos utilizar la variable details (de vuelta al archivo app.js) y aplicarle la propiedad innerHTML para sobrescribir el contenido actual.

5. Así que copiamos el código HTML que abarca el bloque de details (lo puedes ver arriba subrayado en rosa) y sustituimos el código estático (City name, Weather condition, ...) por código dinámico. Con "código dinámico" nos referimos a los datos reales, a los cuales tenemos acceso a través de nuestras variables cityDetails weather. 

Para saber exactamente qué propiedades necesitamos, vamos a nuestra consola y las consultamos ahí. Por ejemplo, para saber el nombre de la ciudad, lo obtenemos a través de cityDetails --> EnglishName

  • cityDetails:
    • AdministrativeArea: {ID: "NH", LocalizedName: "North Holland", EnglishName: "North Holland", Level: 1, LocalizedType: "Province", …}
    • Country: {ID: "NL", LocalizedName: "Netherlands", EnglishName: "Netherlands"}
    • DataSets: (3) ["Alerts", "MinuteCast", "Radar"]
    • EnglishName: "Amsterdam"

Y así para todas las propiedades que necesitemos.

details.innerHTML = `
  <h5 class="my-3">${cityDetails.EnglishName}</h5>
  <div class="my-3">${weather.WeatherText}</div>
  <div class="display-4 my-4">
   <span>${Math.floor(weather.Temperature.Metric.Value)}</span>
   <span>°C</span>
  </div>
`;

He utilizado el Math.floor() porque no necesito decimales en la temperatura.

¡Y voilá! Ya podemos ver la información de cualquier ciudad en la pantalla. 👀 👏

html template resultado

6. Pero no tiene sentido que se vea la tarjeta por defecto, porque en ese momento el usuario no ha hecho ninguna búsqueda. Así que la ocultamos hasta que el usuario busque algo. Para ello, le aplicamos la clase de bootstrap d-none a la card en el index.htmlEsta clase aplica la propiedad de CSS "display: none".

<div class="card shadow rounded d-none"></div>

Dentro de la función updateUI, comprobamos si la variable card tiene la clase d-none. Si la tiene, significa que el usuario aún no ha hecho ninguna búsqueda. Pero, en el contexto de esta función, el usuario ya ha hecho una búsqueda, así que eliminamos la clase. 

    // check if d-none is present
    if(card.classList.contains('d-none')) {
        card.classList.remove('d-none');
    }

¡Y ya lo tenemos! 😋 👍

Destructuring en acción

Existe una característica añadida a JS recientemente llamada destructuring. Utilizamos destructuring para obtener propiedades de un objeto de manera más sencilla. Así, utilizando destructuring conseguimos escribir menos código y más limpio. Para poder utilizarlo, tomamos este bloque como ejemplo:

const updateUI = (data) => {
 
    const cityDetails = data.cityDetails;
    const weather = data.weather;

El parámetro data es un objeto que contiene a su vez dos objetos como propiedades (cityDetails weather).

> {cityDetails: {…}, weather: {…}}

Para poder acceder a las propiedades de esos objetos más fácilmente, nos hemos creado las variables cityDetails weather, que como ves, coinciden en nombre con las propiedades de los objetos. Gracias a esto, podemos usar destructuring. 

    // const cityDetails = data.cityDetails;
    // const weather = data.weather;

    // destructuring
    const { cityDetails, weather } = data;

Y con este cambio, todo debería funcionar igual que antes, sólo que ahora nuestro código está más optimizado. ✌ 😏

THE END!

¡Y con esto terminamos la parte #2 de este proyecto! Espero que hayas aprendido algo nuevo 😊.  Si te queda alguna duda, ¡nos vemos en los comentarios! Si quieres seguir aprendiendo, no te pierdas la parte #3  (disponible próximamente).

Y si crees que este post puede serle útil a alguien, ¡compártelo!

Otros artículos que pueden interesarte

Días del 353 al 386
Objetivos versus realidad Y nuevamente, llegó otro día clave. Llegó…y pasó. El pasado 4 de marzo este Reto Computer Geek[...]
Angular: Entendiendo la Directiva ngModel
Angular es un framework que nos permite, entre otras cosas, añadir contenido dinámico a nuestros archivos HTML. Una de las formas[...]
Días del 2 al 4
"Si buscas resultados distintos no hagas siempre lo mismo" - Albert EinsteinEstos días estoy aprendiendo a hacer loops en JavaScript[...]
Compartir:

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

¡Suscríbete!

Así serás la primera persona en enterarte de la publicación de artículos como éste.

Como toda web legal que se precie, utilizamos cookies para asegurar que damos la mejor experiencia al usuario en nuestro sitio web. Si continúas utilizando este sitio asumiremos que estás de acuerdo. más información

Los ajustes de cookies de esta web están configurados para "permitir cookies" y así ofrecerte la mejor experiencia de navegación posible. Si sigues utilizando esta web sin cambiar tus ajustes de cookies o haces clic en "Aceptar" estarás dando tu consentimiento a esto.

Cerrar