Task 3: Build the Weather App

The next steps involve completing the placeholder functions in the starter code that will enable the user to make API calls and update the user interface.

Step 1: Clone the starter code

Clone the starter code:

git clone https://github.com/cs280fa23/weather-app -b starter

Notice the starter is a Vite app. You should install the dependencies by a pnpm install and then run the app in development by a pnpm dev command. Point your browser to http://localhost:5173/weather-app/:

When you first run this weather application, the system will attempt to retrieve the AccuWeather API key from your browser's local storage. Local storage is a way for web applications to store key-value pairs in a web browser, allowing data to persist even after the browser tab has been closed.

If the application does not find an API key in local storage, you will be greeted with an alert window. This alert will instruct you to store your AccuWeather API key in local storage under the key 'accuweather_api_key'.

The reason we use local storage for the API key is to keep it secure and avoid hard-coding it directly into the JavaScript file. This practice ensures that the key remains private and is not exposed to anyone who might view the application's source code.

To store your API key in local storage and avoid seeing the alert in the future, you can use the following command in your browser's JavaScript console:

localStorage.setItem("accuweather_api_key", "YOUR_API_KEY");

Make sure to replace "YOUR_API_KEY" with your actual AccuWeather API key. After this step, you can refresh the application, and it should work seamlessly without showing the alert.

Remember: Treat your API key as a password. Do not share it with others, and avoid exposing it in public forums or repositories.

Step 2: Review Layout and Styling

As is customary in this course, the layout and styling have been provided. However, the app is not fully functional, as most of the required JavaScript code is missing.

Open the style.css file:

@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';

body {
  background: url(/background.svg) no-repeat center center fixed;
  -webkit-background-size: cover;
  -moz-background-size: cover;
  -o-background-size: cover;
  background-size: cover;
}

This minimal styling loads the background image (public/background.svg). The Gradient Topography image available at https://coolbackgrounds.io/ was downloaded and renamed as background.svg.

Open the index.html file and note the layout:

<div class="flex flex-col justify-between h-screen p-10 text-3xl">
  <header class="flex flex-col w-full gap-5">
    <h1 class="text-4xl font-semibold">Weather App</h1>
    <form id="search">
      <input
        id="city"
        name="city"
        class="w-full p-4 border-2"
        type="text"
        placeholder="Enter a location to get weather forecast"
      />
    </form>
  </header>
  <footer class="flex justify-between">
    <div id="name" class="">City Name</div>
    <div id="condition" class="">Weather Condition</div>
    <div id="temperature" class="">Temperature</div>
  </footer>
</div>

Step 3: Review starter code

Open the src/main.js file. Let's break down the starter code for our Weather App to understand its core components.

1. Imports and Constants:

import "../style.css";

This line imports the CSS styling for our web application, making it look aesthetically pleasing.

const BASE_URL = "https://dataservice.accuweather.com";

Here, we're defining a constant BASE_URL which is the base endpoint for all our AccuWeather API requests. It's the starting point to which we'll append specific paths for different data.

2. Retrieving the API Key:

let API_KEY = localStorage.getItem("accuweather_api_key");

We try to get the AccuWeather API key from the browser's local storage. If it's there, great! If not, the API_KEY will be null.

3. Checking the API Key:

if (!API_KEY) {
  alert(
    "Please store your API key in local storage with the key 'accuweather_api_key'.",
  );
} else {
  const search = document.getElementById("search");
  search.addEventListener("submit", getWeatherForecast);
}

If the API key is not found (!API_KEY), an alert is shown to the user to instruct them about storing their API key. If the key is present, an event listener is attached to the "search" form which listens for the "submit" event.

4. Handling Form Submission:

function getWeatherForecast(event) {
  event.preventDefault();
  const city = document.getElementById("city").value.trim();
  document.getElementById("city").value = "";
  getLocationKey(city);
}

When the form is submitted, this function is triggered. It prevents the default form submission action (which would reload the page) using event.preventDefault(). It then retrieves the entered city name, trims any whitespace, and calls the getLocationKey function with the city name.

5. Getting the Location Key:

function getLocationKey(city) {
  // TODO get the "location key" for the given `city`!
  //  then call getCurrentCondition to retrieve weather forecast for it!
  console.log(city);
}

This function is a placeholder for you to complete. You'll fetch the location key for the given city using the AccuWeather API. Once you retrieve the location key, you'll call the getCurrentCondition function.

6. Getting Current Weather Condition:

function getCurrentCondition(location) {
  // TODO get the "current condition" based on the `location` argument!
  //  then call updateUI to update the UI!
}

Another placeholder function. Here, you're expected to use the AccuWeather API again, but this time to get the current weather condition for the provided location. After getting the weather data, you'll call the updateUI function.

7. Updating the UI:

function updateUI(location, forecast) {
  // TODO update the following based on `location` and `forecast` arguments!
  document.getElementById("name").innerText = "City Name";
  document.getElementById("condition").innerText = "Weather Condition";
  document.getElementById("temperature").innerText = "Temperature";
}

The final placeholder function. Here, you'll take the provided location and forecast data and update the webpage's user interface to show the weather details for the selected city.

To summarize, this starter code provides the foundation for building a weather app using the AccuWeather API. Your task will be to complete the placeholder functions, making API calls and updating the UI accordingly.

Step 4: Implement getLocationKey

We need to implement getLocationKey and getCurrentCondition functions that call the AccuWeather API, similar to what we did in Postman. In JavaScript, we can use the Fetch Web API to make HTTP requests.

The Fetch API provides a JavaScript interface for making HTTP requests. The simplest syntax looks like this:

fetch('/some/api/endpoint/')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(err => console.log(err))

Calling fetch() returns a Promise. We can then “wait” for the promise to resolve. Once resolved, the response will be available within the promise's then() method. Using fetch() is an example of retrieving resources asynchronously across the network.

We will explore JavaScript's asynchronous behavior in the near future.

Open main.js and update the implementation of getLocationKey:

function getLocationKey(city) {
  fetch(`${BASE_URL}/locations/v1/cities/search?apikey=${API_KEY}&q=${city}`)
    .then(response => response.json())
    .then(data => {
      const location = data[0];
      console.log(location);
    })
    .catch(err => console.log(err));
}

This function makes an API request (HTTP GET request) using the fetch API.

The resulting data variable is an array of objects, where each object corresponds to a city or location that matched the city argument. For simplicity, we take the first location (i.e., data[0]) as our search result.

Try the application by entering Baltimore as the city.

Revisit the implementation of getLocationKey and change the console log statement to a function call to getCurrentCondition to set the scene for the next step.

-   console.log(location);
+   getCurrentCondition(location);

Step 5: Implement getCurrentCondition

Let's implement the getCurrentCondition function:

function getCurrentCondition(location) {
  fetch(`${BASE_URL}/currentconditions/v1/${location.Key}?apikey=${API_KEY}`)
    .then((response) => response.json())
    .then((data) => {
      const forecast = data[0];
      console.log(forecast);
    })
    .catch((err) => console.log(err));
}

The data variable will be an array that contains one object. That object includes the weather forecast for the given location.

Try the application by entering Baltimore as the city.

Revisit the implementation of getCurrentCondition and change the console log statement to a function call to updateUI to set the scene for the next step.

-   console.log(forecast);
+   updateUI(location, forecast);

Step 6: Implement updateUI

Let's update the updateUI function:

function updateUI(location, forecast) {
  document.getElementById("name").innerText = location.LocalizedName;
  document.getElementById("condition").innerText = forecast.WeatherText;
  document.getElementById("temperature").innerHTML = `${forecast.Temperature.Imperial.Value} &#8457`;
}

Notice the last line of code sets the inner html of an HTML element with the ID "temperature" to a string that includes the value of the temperature in Fahrenheit and the Fahrenheit symbol (°F). The temperature value is accessed from the Temperature object within the forecast object, and the Imperial.Value property specifies the temperature value in Fahrenheit. The &#8457; is the HTML entity for the Fahrenheit symbol, which is added to the end of the string to indicate the unit of measurement.

Try the application by entering Baltimore as the city.