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 trial

JavaScript

Help 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
Blake Larson
13,014 Points

Hey, 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');
    } 
}

Thank 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

Blake Larson
Blake Larson
13,014 Points

That is a very clean UI. Looks great.

thank you!