Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Phoenix - Alejandra Guevara and Tram Hoang #9

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
4 changes: 0 additions & 4 deletions .gitignore

This file was deleted.

47 changes: 45 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,15 +1,58 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Weather Report</title>
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Rubik&display=swap" rel="stylesheet">
<link rel="stylesheet" href="styles/index.css" />
<link rel="stylesheet" href="styles/index.css">
</head>

<body>
<header class="header__header">
<h1>Weather Report</h1>
<span>For the lovely city of
<span id="headerCityName" class="header__city-name">Seattle</span></span>
</header>
<section class="temperature__section">
<h2>Temperature</h2>
<div class="temperature__content">
<div class="temperature__controls">
<span id="increaseTempControl">⬆️</span>
<span id="tempValue"></span>
<span id="decreaseTempControl">⬇️</span>
</div>
<button id="currentTempButton">Get Realtime Temperature</button>
</div>
</section>
<section class="sky__section">
<h2>Sky</h2>
<select id="skySelect">
<option value="Sunny">Sunny</option>
<option value="Cloudy">Cloudy</option>
<option value="Rainy">Rainy</option>
<option value="Snowy">Snowy</option>
<option value="Misty">Misty</option>
</select>
</section>

<section class="city-name__section">
<h2>City Name</h2>
<input type="text" id="cityNameInput">
<button id="cityNameReset" class="city-name__reset-btn">Reset</button>
</section>

<section class="garden__section">
<h2>Weather Garden</h2>
<div id="gardenContent" class="garden__content">
<div id="sky"></div>
<div id="landscape"></div>
</div>
</section>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="./src/index.js"></script>
</body>
</html>
</html>
136 changes: 136 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
let currentTemperature = 70;

const tempValue = document.getElementById('tempValue');
const increaseTempControl = document.getElementById('increaseTempControl');
const decreaseTempControl = document.getElementById('decreaseTempControl');
const landScrape = document.getElementById('landscape');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: typo

const landScape = document.getElementById('landscape');

const skySelect = document.getElementById('skySelect');
const skyElement = document.getElementById('sky');
Comment on lines +3 to +8

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we import this script file at the end of our HTML, these lookups should all work as expected. But we generally don't want to make this assumption, instead, preferring to perform lookups like this in response to the DOMContentLoaded event.



const updateTemperature = () => {
tempValue.textContent = `${currentTemperature}°F`;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider splitting this into two functiosn, one that is able to figure out which data to use to update the screen, and another that actually applies the supplied values to the screen.


if (currentTemperature >= 80) {
tempValue.style.color = 'red';
tempValue.style.backgroundColor = '#FFCCCC';
Comment on lines +15 to +16

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting the style attribute is the equivalent of hard coding a style in HTML. In general, prefer to assign a class to an element, and use style rules to effect any desired UI formatting changes.

landScrape.textContent = "🌵__🐍_🦂_🌵🌵__🐍_🏜_🦂";
} else if (currentTemperature >= 70) {
Comment on lines +14 to +18

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This large chained if statement is repetative. If we stored the relevant data in a way that we could loop through (direct key lookup isn't as helpful, since our temperatures have ranges) we could make our application more data driven, so that just by changing a data structure (that maybe we could load from an API call) there could be different temperature behaviors.

Consider how we could use a structure like the following:

// each row holds the bottom of the temperature range, a class name, and a landscape string
const TEMPERATURE_DATA = [
    [80, 'very-hot-temp', '🌵__🐍_🦂_🌵🌵__🐍_🏜_🦂'],
    [70, 'hot-temp', '🌸🌿🌼__🌷🌻🌿_☘️🌱_🌻🌷'],
    [60, 'warm-temp', '🌾🌾_🍃_🪨__🛤_🌾🌾🌾_🍃'],
    [50, 'cool-temp', '🌲🌲🍂🌲🍁🍃🌲🌾🌲🌲🍂🍁🌲'],
    [40, 'cold-temp', '🌲🌲⛄️🌲⛄️🍂🌲🍁🌲🌲⛄️🍂🌲'],
];

tempValue.style.color = 'orange';
tempValue.style.backgroundColor = '#FFE4B5';
landScrape.textContent = "🌸🌿🌼__🌷🌻🌿_☘️🌱_🌻🌷";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer ' rather than " for JS strings

} else if (currentTemperature >= 60) {
tempValue.style.color = 'yellow';
tempValue.style.backgroundColor = 'pink';
landScrape.textContent = "🌾🌾_🍃_🪨__🛤_🌾🌾🌾_🍃";
} else if (currentTemperature >= 50) {
tempValue.style.color = 'green';
tempValue.style.backgroundColor = '#CCFFCC';
landScrape.textContent = "🌲🌲🍂🌲🍁🍃🌲🌾🌲🌲🍂🍁🌲";
} else if (currentTemperature >= 40) {
tempValue.style.color = 'teal';
tempValue.style.backgroundColor = '#CCFFFF';
landScrape.textContent = "🌲🌲⛄️🌲⛄️🍂🌲🍁🌲🌲⛄️🍂🌲";
}
};

increaseTempControl.addEventListener('click', () => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should generally wait to register events until afetr the DOMContentLoaded event has fired.

currentTemperature += 1;
updateTemperature();
});

decreaseTempControl.addEventListener('click', () => {
currentTemperature -= 1;
updateTemperature();
});

// Wave 5
const updateSky = (skyType) => {
const skyOptions = {
Sunny: "☁️ ☁️ ☁️ ☀️ ☁️ ☁️",
Cloudy: "☁️☁️ ☁️ ☁️☁️ ☁️ 🌤 ☁️ ☁️☁️",
Rainy: "🌧🌈⛈🌧🌧💧⛈🌧🌦🌧💧🌧🌧",
Snowy: "🌨❄️🌨🌨❄️❄️🌨❄️🌨❄️❄️🌨🌨",
Misty: "🌫️🌫️💨🌫️🌦🌫️🌫️💨🌫️🌫️🌫️💨",
};
Comment on lines +49 to +55

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way we could use data like this to fill in the available sky picks in the UI rather than hard coding them in the HTML?


skyElement.textContent = skyOptions[skyType] || skyOptions["Sunny"];
skySelect.value = skyType; // Sync the dropdown
};

skySelect.addEventListener("change", () => {
const selectedSky = skySelect.value;
updateSky(selectedSky);
});

// Wave 3
document.addEventListener("DOMContentLoaded", () => {
const cityNameInput = document.getElementById('cityNameInput');
const headerCityName = document.getElementById('headerCityName');
const currentTempButton = document.getElementById("currentTempButton");

cityNameInput.addEventListener("input", (event) => {
const newCityName = event.target.value;
headerCityName.textContent = newCityName;
});

// Wave 4
const getRealTimeTemperature = async () => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try to avoid defining large functions here in the handler for DOMContentLoaded. Or if you are doing so in order to use the handler to provide scope restrictions for your other variables and functions, then do so consistently. (e.g., move all of the other globals here, or move these to be globals).

If everything were moved here, then the variables that lookup the elements could all legitimately by const values. If they were defined globally, but only resolved here, then they would need to be defined as let variables.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than implementing this as a single monolithic function, consider decomposing this into a few smaller functions.

  1. a function getLatLonForLocation that takes a name to query and returns {lat, lon} data
  2. a function getWeatherForLatLon that takes a {lat, lon} data object and returns weather data

These two functions could be chained together through a third function getWeatherForLocation that takes a name to query and returns weather data by making use of the two smaller functions.

Additional functions could use this overall function to update the UI using the weather data results. While writing one long function can be helpful when trying to get everything working, it's worth goin back, identifying key pieces, and extracting them into helper functions with descriptive names, as this helps our code be more self-documenting.

const cityName = headerCityName.textContent;

try {
const locationResponse = await axios.get(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice use of async/await to simplify the logic flow.

`http://127.0.0.1:5000/location?q=${cityName}`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than hard-coding the URL, it can be helpful to store the base url in a variable that we also interpolate into the string here. This can make deployment easier (since we'll need to change any dev-routed endpoints to point to their production locations).

const BASE_URL = 'http://127.0.0.1:5000';

then here

                `${BASE_URL}/location?q=${cityName}`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer to pass query params using a data structure if supported by the HTTP library (axios does), as this can prevent unexpected values being injected into the query string. Notice that here, the user's city name input is directly injected into the URL. They could include their own query params in the input (e.g., Seattle&token=1234) which would get added to url as additional params. With our current system, this doesn't lead to a problem, but there could be ways for users to issue problematic requests this way. By making use of any library method for adding params, this will usually encode the params in a way so that they won't be misinterpretted.

axios.get(url, {
  params: {
    q: cityName,
  },
});

);

const { lat, lon } = locationResponse.data[0];

const weatherResponse = await axios.get(
`http://127.0.0.1:5000/weather?lat=${lat}&lon=${lon}`
);

const tempKelvin = weatherResponse.data.main.temp;
const tempFahrenheit = ((tempKelvin - 273.15) * 1.8) + 32;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider making a conversion helper function.


currentTemperature = Math.round(tempFahrenheit);
updateTemperature();

// Update the sky based on weather condition
const weatherCondition = weatherResponse.data.weather[0].main;
let skyType = "Sunny"; // Default sky

if (weatherCondition.includes("Rain")) {
skyType = "Rainy";
} else if (weatherCondition.includes("Snow")) {
skyType = "Snowy";
} else if (weatherCondition.includes("Cloud")) {
skyType = "Cloudy";
} else if (weatherCondition.includes("Misty")) {
skyType = "Misty";
}

updateSky(skyType);
} catch (error) {
console.error("Error fetching weather data:", error);
}
};

currentTempButton.addEventListener("click", getRealTimeTemperature);

// Wave 6
const resetButton = document.getElementById('cityNameReset');
resetButton.addEventListener("click", () => {
cityNameInput.value = "";
headerCityName.textContent = "Seattle";
currentTemperature = 70;
updateTemperature();
updateSky("Sunny");
// skySelect.value = "Sunny";
});

updateTemperature();
updateSky("Sunny");
});



170 changes: 170 additions & 0 deletions styles/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
h2 {
margin: 0 auto 2rem auto;
}

body {
display: grid;
grid-template-columns: 1fr 2fr;
grid-template-rows: auto auto auto auto;
grid-gap: 1rem;

font-family: "Rubik", sans-serif;
font-size: 18px;
background-color: #1b69f9;
margin: 2rem;
}

.header__header {
color: white;
grid-column: span 3;
display: flex;
align-items: center;
margin: 2rem auto 3rem 0;
}

.header__header > h1 {
margin-right: 2rem;
font-size: 3em;
}

.header__city-name {
font-style: oblique;
font-size: 2rem;
}

.header__city-name::before,
.header__city-name::after {
content: "✨";
}

.temperature__section,
.sky__section,
.city-name__section {
border-radius: 8px;
padding: 2rem;
background-color: white;
}

.temperature__section {
grid-row: 2;
}

.temperature__section button {
background-color: #1b69f9;
border: none;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
border-radius: 10px
}

.sky__section {
grid-row: 3;
}

.city-name__section {
grid-row: 4;
}

.garden__section {
grid-row: 2 / span 3;
grid-column: 2;
text-align: center;
align-self: center;
}

.temperature__content {
display: flex;
flex-direction: row;
justify-content: space-around;
/* justify-content: center; */
}

#tempValue {
font-size: 3rem;
margin-left: 1.5rem;
/* padding-right: 1rem; */
/* margin-right: 1.5rem; */
}

.temperature__controls {
display: flex;
flex-direction: column;
align-items: center;
}

.garden__section > h2 {
color: white;
}

.garden__content {
min-height: 200px;
max-width: fit-content;
margin: auto;
padding: 2rem;

display: flex;
flex-direction: column;
justify-content: space-between;

border-radius: 8px;
font-size: 2em;
}

.city-name__reset-btn {
border: 0;
background-color: #1655cc;
color: white;
border-radius: 8px;
padding: 1rem;
font-family: "Rubik", sans-serif;
}

/* .red {
color: red;
}

.orange {
color: orange;
}

.yellow {
color: gold;
}

.yellow-green {
color: yellowgreen;
}

.green {
color: green;
}

.teal {
color: teal;
}

.cloudy {
background-color: lightgrey;
}

.sunny {
background-color: rgb(221, 255, 255);
}

.rainy {
background-color: lightblue;
}

.snowy {
background-color: lightsteelblue;
} */

#skyDisplay {
font-size: 2rem;
text-align: center;
margin-top: 10px;
}