Welcome to the Treehouse Community
Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.
Looking to learn something new?
Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.
Start your free trialKate Johnson
Python Development Techdegree Graduate 20,155 PointsHelp with buttons
I am making a simple weather app that loads your past searched for locations as buttons. As you can see in the code, once you search for a location it gets saved as an object with a "searched" property of "false." Right now the application works perfectly, but I would like to optimize it by being able to change the searched property to "true" or add some logic somewhere in the code so that it does not create a button if you search for the same city twice (right now if you searched for NYC more than once, it would create a button for NYC each time). Any ideas on how I can achieve this? Thank you!
// selector helpers
const d=document;
const q=(e,n=d)=>n.querySelector(e);
const qa=(e,n=d)=>n.querySelectorAll(e);
// html elements
const cityFormEl = q("#city-form")
const cityInputEl = q("#cityname");
const cityContainerEl = q("#city-container")
const daysContainerEl = q("#days-container");
const citySearchTerm = q("#city-search-term");
const pastCitiesButtonsEl = q("#past-cities-buttons");
const searchBtn=q("#getCords");
// get current date
const months = ["January","February","March","April","May","June","July","August","September","October","November","December"];
const date = new Date();
let month = months[date.getMonth()];
let day = date.getDate()
let currentDate = `${month}, ${day}`
var getWeather = function(lat,lon,city) {
cityInputEl.value = "";
daysContainerEl.innerHTML = "";
//format the OpenWeather api url
var apiUrl = `https://api.openweathermap.org/data/2.5/onecall?lat=${lat}&lon=${lon}&units=imperial&appid=fb9174eee39da62906652ee7dd116b7c`
var currentCity = city
console.log(currentCity)
//make a request to the url
fetch(apiUrl)
.then(function(response) {
// request was successful
if (response.ok) {
response.json().then(function(data) {
console.log(data)
displayWeather(data, currentCity)
});
} else {
alert("Error: location not found!");
}
})
.catch(function(error) {
alert("Unable to connect to weather app");
});
};
var initialize = function(event) {
event.preventDefault();
var address = cityInputEl
var autocomplete = new google.maps.places.Autocomplete(address);
autocomplete.setTypes(['geocode']);
google.maps.event.addListener(autocomplete, "place_changed", function() {
var place = autocomplete.getPlace();
if (!place.geometry) {
return;
}
var address = "";
if (place.address_components) {
address = [
(place.address_components[0] && place.address_components[0].short_name || ""),
(place.address_components[1] && place.address_components[1].short_name || ""),
(place.address_components[2] && place.address_components[2].short_name || "")
].join(" ");
}
});
}
var codeAddress = function() {
geocoder = new google.maps.Geocoder();
var city = cityInputEl.value;
geocoder.geocode({
'address': city
}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
var lat = results[0].geometry.location.lat();
var lon = results[0].geometry.location.lng();
getWeather(lat,lon,city);
var cityObj = {
cityname: city,
searched: false
}
saveSearch(cityObj)
makeBtn(city)
} else {
console.log("Geocode was not successful for the following reason: " + status);
}
});
}
var displayWeather = function (data, currentCity) {
// current forecast element
cityContainerEl.className = "card"
citySearchTerm.textContent = `${currentCity}, ${currentDate}`
q("#current-icon").innerHTML = `<img src='http://openweathermap.org/img/wn/${data.current.weather[0].icon}@2x.png' >`
q("#current-temp").textContent = `Temp: ${data.current.temp}°F`
q("#current-wind").textContent = `Wind: ${data.current.wind_speed} MPH`
q("#current-humidity").textContent = `Humidity: ${data.current.humidity}%`
let uviEl = q("#current-uvi")
let uvi = Math.round(data.current.uvi)
uviEl.textContent = `UVI: ${data.current.uvi}`
if (uvi <= 2){
uviEl.style.backgroundColor = "green"
} else if (uvi >= 3 && uvi <= 5){
uviEl.style.backgroundColor = "yellow"
} else if (uvi >= 6 && uvi <= 7) {
uviEl.style.backgroundColor = "orange"
} else if (uvi >= 8 && uvi <= 10) {
uviEl.style.backgroundColor = "red"
} else if (uvi >= 11) {
uviEl.style.backgroundColor = "magenta"
}
// 5 day forecast subtitle
var fiveDaysubtitle = document.createElement("h2")
fiveDaysubtitle.textContent = "5-Day Forecast"
fiveDaysubtitle.className = "subtitle"
fiveDaysubtitle.id = "5-day-forcast"
daysContainerEl.appendChild(fiveDaysubtitle);
// day cards wrapper div
var dayCardWrapper = document.createElement("div")
dayCardWrapper.className = "day-card-wrapper"
daysContainerEl.appendChild(dayCardWrapper);
// day card loop
for (var i=1; i<=5; i++) {
var dayHeader = document.createElement("h3")
dayHeader.textContent = `${month}, ${day + i}`
dayHeader.className = "card-header text-uppercase"
dayCardWrapper.appendChild(dayHeader);
var dayCard = document.createElement("div")
dayCard.className = "day-card-body"
dayHeader.appendChild(dayCard)
// weather icon image
var weatherIcon = document.createElement("p")
weatherIcon.innerHTML = `<img src='http://openweathermap.org/img/wn/${data.daily[i].weather[0].icon}@2x.png' >`
dayCard.appendChild(weatherIcon)
// temp
var dayTemp = document.createElement("p")
dayTemp.textContent = `Temp: ${data.daily[i].temp.day}°F`
dayCard.appendChild(dayTemp)
// wind
var dayWind = document.createElement("p")
dayWind.textContent = `Wind: ${data.daily[i].wind_speed} MPH`
dayCard.appendChild(dayWind)
// humidity
var dayHumidity = document.createElement("p")
dayHumidity.textContent = `Humidity: ${data.daily[i].humidity}%`
dayCard.appendChild(dayHumidity)
}
}
function saveSearch(cityObj) {
var pastSearches = loadPastSearches();
pastSearches.push(cityObj);
localStorage.setItem("cityObjects", JSON.stringify(pastSearches))
}
function loadPastSearches() {
var pastSearchArr = JSON.parse(localStorage.getItem("cityObjects"));
if (!pastSearchArr || !Array.isArray(pastSearchArr)) return []
else return pastSearchArr
}
var makePastBtns = function() {
var pastCities = loadPastSearches()
for (var city of pastCities) {
var pastSearchBtn = document.createElement("button")
pastSearchBtn.className = "btn past-search-btn"
pastSearchBtn.textContent = city.cityname
pastCitiesButtonsEl.appendChild(pastSearchBtn);
pastSearchBtn.addEventListener ("click", function() {
geocoder = new google.maps.Geocoder();
var citySelection = city.cityname
geocoder.geocode({
'address': citySelection
}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
var lat = results[0].geometry.location.lat();
var lon = results[0].geometry.location.lng();
getWeather(lat,lon, citySelection);
} else {
console.log("Geocode was not successful for the following reason: " + status);
}
})
});
}
}
var makeBtn = function(city) {
var pastSearchBtn = document.createElement("button")
pastSearchBtn.className = "btn past-search-btn"
pastSearchBtn.textContent = city
pastCitiesButtonsEl.appendChild(pastSearchBtn);
pastSearchBtn.addEventListener ("click", function() {
geocoder = new google.maps.Geocoder();
geocoder.geocode({
'address': city
}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
var lat = results[0].geometry.location.lat();
var lon = results[0].geometry.location.lng();
getWeather(lat,lon,city);
} else {
console.log("Geocode was not successful for the following reason: " + status);
}
})
});
}
// event listeners
google.maps.event.addDomListener(window, "load", initialize);
searchBtn.addEventListener("click", codeAddress)
q("#clear-btn").addEventListener("click", function() {
[ ... qa(".past-search-btn") ].map(
thisButton => thisButton.remove());
})
makePastBtns();
<!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">
<link href="https://fonts.googleapis.com/css?family=IBM+Plex+Sans:400,400i,700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css">
<link rel="stylesheet" href="assets/css/style.css" />
<title>Weather Dashboard</title>
</head>
<body class="flex-column min-100-vh">
<header class="hero">
<h1 class="app-title">Weather Dashboard</h1>
</header>
<main class="flex-row justify-space-between">
<div class="col-12 col-md-4">
<div class="card">
<h3 class="card-header text-uppercase">Search for a City:</h3>
<form name='city' id="city-form" class="card-body">
<label class="form-label">
City Name
<input name="cityname" id="cityname" type="text" autofocus class="form-input" />
</label>
<input type='hidden' name='address' />
<button id="getCords" type='button' class="btn">Search</button>
</form>
</div>
<div class="card">
<h3 class="card-header text-uppercase">Recent Searches </h3>
<div class="card-body" id="past-cities-buttons">
<button class="btn" id="clear-btn">Clear</button>
</div>
</div>
</div>
<!-- current forecast-->
<div class="col-12 col-md-8">
<div id="city-container">
<h2 id="city-search-term" class="subtitle"></h2>
<div id="current-icon"></div>
<p id="current-temp"></p>
<p id="current-wind"></p>
<p id="current-humidity"></p>
<p id="current-uvi"></p>
</div>
<!-- 5 day forecast -->
<div id="days-container"></div>
</div>
</main>
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=places&key=AIzaSyBgBIzIu3OR52otMyzr8E8HU4Z3YzSMvIE"></script>
<script src="assets/js/script.js"></script>
</body>
</html>
2 Answers
Blake Larson
13,014 PointsHey, cool app! This might not be the most elegant solution but it would work. Basically checking the searches in storage for a match and skipping the geocode api call if the city has been searched.
var codeAddress = function() {
geocoder = new google.maps.Geocoder();
var city = cityInputEl.value;
// set a search boolean and grab the local storage searches
//
let alreadySearched = false;
const citiesSearched = JSON.parse(localStorage.getItem('cityObjects'));
// If there are previous cities searched loop through and check for a 'cityname'
// match with the input value
if(citiesSearched) {
citiesSearched.forEach(c => {
if(c.cityname === city) {
alreadySearched = true;
}
});
}
// skip api call and handle user experience on duplicates
if(!alreadySearched) {
//if a new city is searched make the api call.
geocoder.geocode({
'address': city
}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
var lat = results[0].geometry.location.lat();
var lon = results[0].geometry.location.lng();
getWeather(lat,lon,city);
// Doesn't use the 'searched' property on the recent objects. Depending on other solutions
// you could possible remove that if you wanted but I didn't look to see if it was being
// used for something else.
var cityObj = {
cityname: city,
searched: false
}
// If check makes sure these functions are not called.
saveSearch(cityObj)
makeBtn(city)
} else {
console.log("Geocode was not successful for the following reason: " + status);
}
});
} else {
// You can call another function for something like an alert or modal
// to be created to tell the user it's been searched.
console.log('already searched');
}
}
Blake Larson
13,014 PointsThat is a very clean UI. Looks great.
Kate Johnson
Python Development Techdegree Graduate 20,155 Pointsthank you!
Kate Johnson
Python Development Techdegree Graduate 20,155 PointsKate Johnson
Python Development Techdegree Graduate 20,155 PointsThank you so much! That makes a ton of sense to perform a check right after the search query and I already had a function for returning the past array from storage so this worked perfectly! I really appreciate the help. Feel free to check out the final app! (wouldn't work as nicely without ya) :D