Im trying to build a weather app to learn JS. I want to make the weather stats change with a fade out and then fade in. I cant seem to get it to work, based on what i googled its complicated because i used a "display:none" to hide these elements at first. In case that's the issue what should i've written instead of "display:none"? and how can i do the fadeout/in effect? in case the "display:none" isnt a real issue here, just answer the latter question. Which is the one making me suffer right now.
Its my first post here if i forgot something tell me and i'll edit it into the post! i'll leave a JSFiddle of my code so you can run the code and make the running window bigger. In case you want to read the code here without testing for some reason here's some parts of it (since posting the whole code would take A LOT of lines).
HTML
<div class="containerStats" class="hidden">
<p id="bigPadding">Look up a city for it's weather stats.</p>
<div id="divTemperature" class="hidden">
<p id="temperature">19°</p>
<p id="metricCelcius">C</p>
</div>
<div class="hidden">
<p id="city">Rosario</p>
<div id="iconText">
<p id="weatherText">Sunny</p>
<img id="weatherIcon" src="http://openweathermap.org/img/wn/01d#2x.png" alt="">
</div>
</div>
<div id="spaceBetween" class="hidden">
<div>
<p id="windSpeed"> Wind</p>
<p id="humidity">Humidity</p>
</div>
<div>
<p id="windSpeedKM"> 32km/h</p>
<p id="humidityPorcentage">62%</p>
</div>
</div>
</div>
CSS
.containerStats {
display: flex;
position: absolute;
bottom: 22px;
margin: 20px;
margin-left: 28px;
padding: 10px;
text-align: left;
align-items: center;
justify-content: space-around;
background-color: rgba(34, 32, 32, 0.253);
backdrop-filter: blur(12px);
border-radius: 10px;
}
#divTemperature {
display: flex;
vertical-align: baseline;
}
#temperature {
font-size: 82px;
vertical-align: bottom;
}
#metricCelcius {
font-size: 62px;
font-weight: 300;
margin-right: 22px;
vertical-align: center;
}
#spaceBetween {
display: flex;
justify-content: space-between;
margin-left: 2vw;
}
#windSpeedKM {
margin-left: 4vw;
}
#humidityPorcentage {
margin-left: 4vw;
}
.hidden {
display: none !important;
opacity: 0;
transition: 1s;
}
.show {
transition: 1s;
opacity: 1;
}
#bigPadding {
padding: 25px;
}
JS (ALL)
//Var declarations for search bar.
let inputCity = document.getElementById("searchBar");
let cityName = inputCity.value;
let searchIcon = document.getElementById("searchIcon");
let recentCitySearches = document.querySelectorAll("p.recentCity");
//Var declarations for stats output.
let temperatureOutput = document.getElementById("temperature");
let cityOutput = document.getElementById("city");
let iconOutput = document.getElementById("weatherIcon");
let adjectiveOutput = document.getElementById("weatherText");
let windOutput = document.getElementById("windSpeedKM");
let humidityOutput = document.getElementById("humidityPorcentage");
//API link-creation vars
let api_url = "https://api.openweathermap.org/data/2.5/weather?q=";
let api_key = "&appid=8d6d613f6cb4621a5c237a580219c44c";
let unit = "&units=metric";
let i = 0;
let start = true;
let finishedStatsChange = true;
//Send input to by pressing enter.
inputCity.addEventListener("keydown", enter => {
if (enter.key == "Enter") {
//Change city name variable, and call function for upper case.
cityName = inputCity.value;
let latestSearch = firstUpperCase(cityName);
//Update history to show the city name.
recentCitySearches[i].style.opacity = 0;
recentCitySearches[i].innerHTML = latestSearch;
recentCitySearches[i].style.opacity = 1;
//Clean input text.
inputCity.value = "";
cityName = latestSearch;
i++;
//Finish api url by adding the city name from input, call getData() function-
let full_url = api_url + cityName + unit + api_key;
getData(full_url);
if (i > 2) {
i = 0;
}
}
})
//Send input to by clicking search icon instead of pressing enter.
searchIcon.addEventListener("click", search => {
//Change city name variable, and call function for upper case.
cityName = inputCity.value;
let latestSearch = firstUpperCase(cityName);
//Update history to show the city name.
recentCitySearches[i].style.opacity = 0;
recentCitySearches[i].innerHTML = latestSearch;
recentCitySearches[i].style.opacity = 1;
//Clean input text.
inputCity.value = "";
i++;
cityName = latestSearch;
//Finish api url by adding the city name from input, call getData() function-
let full_url = api_url + cityName + unit + api_key;
getData(full_url);
//Execute hideShow()
hideShow();
if (i > 2) {
i = 0;
}
})
//Function always make first letter upper case.
function firstUpperCase(cityName) {
//Assign touppercase() to first letter of string, then add the rest of the sentence by using the actual sentence with the first letter sliced.
latestSearch = cityName[0].toUpperCase() + cityName.slice(1);
return latestSearch;
}
//Click a city from history and see its weather again. (City 1)
recentCitySearches[0].addEventListener("click", clickHistory => {
cityName = recentCitySearches[0].innerText;
let full_url = api_url + cityName + unit + api_key;
getData(full_url);
})
//Click a city from history and see its weather again. (City 2)
recentCitySearches[1].addEventListener("click", clickHistory => {
cityName = recentCitySearches[1].innerText;
let full_url = api_url + cityName + unit + api_key;
getData(full_url);
})
//Click a city from history and see its weather again. (City 3)
recentCitySearches[2].addEventListener("click", clickHistory => {
cityName = recentCitySearches[2].innerText;
let full_url = api_url + cityName + unit + api_key;
getData(full_url);
})
//Hide initial message and show weather stats.
function hideShow() {
if (start == true) {
let statsHidden = document.querySelectorAll(".hidden");
for (let i = 0; i < statsHidden.length; i++) {
statsHidden[i].classList.replace('hidden', 'show');
}
let initialMessage = document.getElementById("bigPadding");
initialMessage.classList.add("hidden");
}
start = false;
}
//Change background img depending on city.
//Get info with API.
async function getData(full_url) {
const api_respone = await fetch(full_url);
const data = await api_respone.json();
//Save stats in vars.
const cityTemperature = data.main.temp;
const cityHumidity = data.main.humidity;
const cityWindSpeed = data.wind.speed;
const weatherAdjective = data.weather[0].description;
const weatherIcon = data.weather[0].icon;
changeOutput(cityTemperature, cityHumidity, cityWindSpeed, weatherAdjective, weatherIcon);
//Once all stats are replaced, execute hideShow()
hideShow();
}
//Change weather stats info depending on city
function changeOutput(cityTemperature, cityHumidity, cityWindSpeed, weatherAdjective, weatherIcon) {
temperatureOutput.innerText = Math.round(cityTemperature) + "°";
cityOutput.innerText = cityName;
iconOutput.src = "http://openweathermap.org/img/wn/" + weatherIcon + "#2x.png";
adjectiveOutput.innerText = firstUpperCase(weatherAdjective);
humidityOutput.innerText = Math.round(cityHumidity) + "%";
windOutput.innerText = Math.round(cityWindSpeed * 3.6) + "km/h";
finishedStatsChange = true;
}
So while getting this to work i found a number of bad practices which may have been confusing you in your html:
declared some divs with multiple class types:
<div class="class1" class="class2"> this should instead be written as a div within a div:
<div class="class1"><div class="class2"></div></div>
or in one div separated by a space
<div class="class1 class2 class3"></div>
declared nested divs as the same classes, in some scenarios this is okay but for your purpose this was incorrect
so i fixed these in your html and then added some extra css to continue the previous styling:
.containerStats {left: 22px;}
.hidden {
opacity: 0;
-webkit-transition: all 1s ease-in-out;
-moz-transition: all 1s ease-in-out;
-o-transition: all 1s ease-in-out;
transition: all 1s ease-in-out;
}
.show {
opacity: 1;
-webkit-transition: all 1s ease-in-out;
-moz-transition: all 1s ease-in-out;
-o-transition: all 1s ease-in-out;
transition: all 1s ease-in-out;
}
then in order to make the html div contaning the weather stats fade in and out with the css we put in, we need to fade out the block of html, wait for the animation and then fade it back in. this was best achieved through a new function call and a change to a previous function to chain them together.
async function getData(full_url) {
// get data
animateChangeOutput(cityTemperature, cityHumidity, cityWindSpeed, weatherAdjective, weatherIcon);
}
function animateChangeOutput(cityTemperature, cityHumidity, cityWindSpeed, weatherAdjective, weatherIcon) {
weatherStats = document.getElementById('weatherStats');
weatherStats.setAttribute('class', 'hidden');
if (i == 1){
changeOutput(cityTemperature, cityHumidity, cityWindSpeed, weatherAdjective, weatherIcon)
}else {
setTimeout(() => { changeOutput(cityTemperature, cityHumidity, cityWindSpeed, weatherAdjective, weatherIcon); }, 1000);
}
setTimeout(() => { weatherStats.setAttribute('class', 'show'); }, 1000);
}
I feel like this whole page could have been restructured to make these jobs a lot simpler and easier, but this was the best that i could do without making major changes to anything you did
A full JSFiddle can be found here https://jsfiddle.net/q59n37rx/
Related
I'am working on progressbar that change the level when reaching some point.
The code.
I want to change the text under the progressbar when the progressbar reach 100 or above 90.
I could change it once but I want to go to next level, like this.
silver ==> gold ==> diamond ==> and more
const step = 5;
var content=document.getElementById('mylevel').innerHTML;
const updateProgress = () => {
const currentWidth = Number(document.getElementById("progressvalue").style.width.replace( "%", ""));
if (currentWidth>=100) {
return;
}
else {
document.getElementById("progressvalue").style.width = `${currentWidth+step}%`;
}
if (currentWidth > 90) {
document.getElementById("mylevel").textContent = "gold";
document.getElementById("progressvalue").style.width = "0%";
}
if (currentWidth > 90 && content == "gold") {
document.getElementById("mylevel").textContent = "diamond";
document.getElementById("progressvalue").style.width = "0%";
}
}
const restart = () => {
document.getElementById("progressvalue").style.width = "0%";
}
.progress {
background-color: #ededed;
width: 100%;
overflow: hidden;
}
#progressvalue {
height: 40px;
background-color: lightgreen;
width: 0%;
}
<div class="progress">
<div id="progressvalue"></div>
</div>
<p id="mylevel">silver</p>
<br />
<button type="button" onclick="updateProgress()">
Update Progress
</button>
<button type="button" onclick="restart()">
Restart
</button>
When the updateprogress is above 90 the silver change to gold, but I need to change again to diamond when the updateprogress is again above 90.
Am I putting the if condition in a wrong place, I tried many times.
I don't know what I'am missing and am new with JavaScript
I started the code but got help here to make it much better (80% of the code done by
teresaPap thanks)
Update
After closer inspection it is an issue of content not updating you need to put it inside updateProgress() or it will forever remain the initial value.
const step = 5;
const updateProgress = () => {
var content = document.getElementById('mylevel').innerHTML;
//the rest of the code
I do however recommend you to improve your if statements. You only need one if for this task.
A better solution
A better solution would be something like this:
Add a hidden value to keep your level progress
</div>
<p id="hiddenlevel">0</p>
<p id="mylevel">silver</p>
<br />
and css:
#hiddenlevel {
height: 0px;
visibility: hidden;
width: 0%;
}
now that you have a hidden value you can wrap up all future ifs in a single one.
const levels = ["silver", "gold", "diamond"]
var mylevel = Number(document.getElementById("hiddenlevel").innerHTML);
if(currentWidth > 90 && mylevel < levels.length){
document.getElementById("hiddenlevel").textContent = mylevel + 1;
document.getElementById("mylevel").textContent = levels[mylevel + 1];
document.getElementById("progressvalue").style.width = "0%";
}
and just like that you can just add a new level inside the levels array and it will be added without issues.
Update 2
Just noticed I made a mistake!
You don't need a hidden element for this: you might end up having to use hidden elements when using plugins, but it was completely unnecessary here :)
Updated code:
const step = 5;
var mylevel = 0;
const updateProgress = () => {
const currentWidth = Number(document.getElementById("progressvalue").style.width.replace( "%", ""));
if (currentWidth>=100) {
return;
}
else {
document.getElementById("progressvalue").style.width = `${currentWidth+step}%`;
}
const levels = ["silver", "gold", "diamond"];
if(currentWidth > 90 && mylevel < levels.length){
mylevel = mylevel + 1;
document.getElementById("mylevel").textContent = levels[mylevel];
document.getElementById("progressvalue").style.width = "0%";
}
}
const restart = () => {
document.getElementById("progressvalue").style.width = "0%";
}
.progress {
background-color: #ededed;
width: 100%;
overflow: hidden;
}
#progressvalue {
height: 40px;
background-color: lightgreen;
width: 0%;
}
<div class="progress">
<div id="progressvalue"></div>
</div>
<p id="mylevel">silver</p>
<br />
<button type="button" onclick="updateProgress()">
Update Progress
</button>
<button type="button" onclick="restart()">
Restart
</button>
Im working on a project and i have basically some troubles with things for my website.
This one is a bit hard for me, i have some ideas but i dont know how to do them in my javascript code.
I have 98 divs (it's a calendar where you can select everyday differents hours to book slots).
There is a Summary (kinda same thing on commercial website) which i want that it says how many slots you selected. But the problem is that i have like I said 98div so i wanna do it in one function.
On the slots you want to book, you can click on it (it selects it) and if you click on it again it deselects it.
I want that you can select as many slots that you want, and the summary shows how many you selected then you can go to next step.
Here is my code if you guys have some ideas !
function x1(e) {
var target = e.target,
count = +target.dataset.count;
target.style.backgroundColor = count === 1 ? "#707070" : 'black';
target.dataset.count = count === 1 ? 0 : 1;
target.innerHTML = count === 1 ? '' : 'réserver';
target.classList.toggle('Resatxt');
target.classList.toggle('unselectable');
}
Actually this code is for the selection of the slots (it's going background black when you clicl on it, and then back to the normal color when you deselect it).
But i think i can do what i want with this.
I thinked about incrementing +1 when we click on the div but the problem that i dont know how to figure it out is when you deselect it you have to do -1 but im a bit lost.
I tried to be clear but ik that is not really.
If you guys have some ideas, go for it.
Thanks a lot
it's nice to see that your joining the programming community. I hope I understood you correctly and made a simple and minimal example to present you how can you achieve what you want. This is just an idea, don't take it too serious and write your own logic to handle the functionality!
const divs = 98;
const list = document.querySelector("#list");
const selectedList = document.querySelector("#selectedList");
let selected = [];
let elementsAdded = 1;
const onSelectDiv = (element) => {
const elementCopy = element.cloneNode(true);
elementCopy.id += "-copy";
selected = [
...selected,
{
id: elementsAdded,
elementId: element.id
}
];
elementsAdded += 1;
selectedList.appendChild(elementCopy);
};
const onClick = (e) => {
if (e.target.className.includes("selected")) {
e.target.classList.remove("selected");
elementsAdded -= 1;
const elementToDelete = selected.findIndex(
(x) => e.target.id === x.elementId
);
selectedList.removeChild(selectedList.childNodes[elementToDelete + 1]);
selected = selected.filter((x) => x.elementId !== e.target.id);
return;
}
onSelectDiv(e.target);
e.target.className += " selected";
};
for (let i = 0; i < divs; i++) {
const div = document.createElement("div");
div.innerHTML += i;
div.className += "div";
div.id = i;
div.addEventListener("click", function (event) {
onClick(event);
});
list.appendChild(div);
}
.view {
display: flex;
flex-direction: 'column';
}
#list {
display: flex;
width: 400px;
max-width: 500px;
flex-wrap: wrap;
}
.div {
padding: 5px;
background-color: black;
cursor: pointer;
color: white;
border-radius: 10px;
margin: 10px;
}
.selected {
background-color: red;
color: white;
}
<div class="view">
<div>
<p>Elements:</p>
<div id="list">
</div>
</div>
<div>
<p>Selected:</p>
<div id="selectedList">
</div>
</div>
</div>
I'm working on a site where I have written some javascript to create a 'typewriter' type effect for the large middle centered text seen below. The script animates the text so that it types forward letter by letter and then deletes the text before starting a new word. The problem I'm having is that when the text is deleted the paragraph element containing the text is empty and the button below 'jumps' to the position where the text was. I'd like to know a good way to fix this issue, either in the javascript or with a simple css positioning fix. I wondered if there was maybe a way to position the button relative to the top "we create digital products text"?
This is my html:
<div class="agency-hero">
<section class="container">
<div class="hero-text customFadeInUp">
<h1 class="tagLine">
We create digital products
</h1>
<p><span class="txt-type " data-wait="2000" data-words='[" "," Websites "," Web Applications "]'></span></p>
<a href="agency-portfolio-4.html" class="stayPut">
See our work
</a>
</div>
</section>
</div>
and the javascript to animate the text:
const TypeWriter = function(txtElement, words, wait = 3000){
this.txtElement = txtElement;
this.words = words;
this.txt='';
this.wordIndex=0;
this.wait=parseInt(wait,10);
this.type();
this.isDeleting = false;
}
// Type Method
TypeWriter.prototype.type = function() {
//current index of word
const current = this.wordIndex % this.words.length;
//get Full text
const fullTxt = this.words[current];
//check for if currently in the deleting state or not
if(this.isDeleting){
this.txt = fullTxt.substring(0,this.txt.length -1);
}else{
//add a character
this.txt = fullTxt.substring(0,this.txt.length +1);
}
//insert txt into element
this.txtElement.innerHTML = `<span class="txt">${this.txt}</span>`;
// Initial Type Speed
let typeSpeed = 300;
if(this.isDeleting){
typeSpeed /= 2;
}
// If word is complete then move on to next word
if(!this.isDeleting && this.txt == fullTxt){
//make pause at the end
typeSpeed = this.wait;
//set Delete to True
this.isDeleting = true;
} else if(this.isDeleting && this.txt == ''){
this.isDeleting=false;
//move to next word
this.wordIndex ++;
// Pause before start typing
typeSpeed = 500;
}
setTimeout(() => this.type(),typeSpeed);
}
// Init on DOM Load
document.addEventListener('DOMContentLoaded',init);
//Init App
function init(){
const txtElement = document.querySelector('.txt-type');
const words = JSON.parse(txtElement.getAttribute('data-words'));
const wait = txtElement.getAttribute('data-wait');
new TypeWriter(txtElement, words, wait);
}
You can use CSS property min-height to maintain the desired gap between both texts. Take a look at the below codes.
With Text-
body {
background-color: lightblue;
}
h1 {
color:black;
text-align: center;
}
p {
font-family: verdana;
font-size: 40px;
background-color:red;
min-height:20px;
}
p+p {
font-size: 20px;
background-color:orange;
}
<h1>We create Digital Products</h1>
<p>Type Writer</p>
<p>See my work</p>
Without Text
body {
background-color: lightblue;
}
h1 {
color:black;
text-align: center;
}
p {
font-family: verdana;
font-size: 40px;
background-color:red;
min-height:20px;
}
p+p {
font-size: 20px;
background-color:orange;
}
<h1>We create Digital Products</h1>
<p></p>
<p>See my work</p>
p is a block element and its height is getting calculated on the basis of its content. Hope it helps.
If you're asking for relative positioning you use the css position property with a value of relative and use the top and left properties to change it. However, you could also use transform to change the positions.
For example:
button {
position:relative;
top:50vh;
}
//Or
button {
transform: translate(0, 50vh);
}
you can change these depending on how you want it.
In my opinion, if I read it correctly, it looks like you want to keep it there so I'd use absolute positioning.
Such as:
button {
position:absolute;
left:50%;
top:90vh;
//This won't move no matter what
}
You could give your <p> a defined height:
.hero-text p {
height: 20px;
}
const TypeWriter = function(txtElement, words, wait = 3000){
this.txtElement = txtElement;
this.words = words;
this.txt='';
this.wordIndex=0;
this.wait=parseInt(wait,10);
this.type();
this.isDeleting = false;
}
// Type Method
TypeWriter.prototype.type = function() {
//current index of word
const current = this.wordIndex % this.words.length;
//get Full text
const fullTxt = this.words[current];
//check for if currently in the deleting state or not
if(this.isDeleting){
this.txt = fullTxt.substring(0,this.txt.length -1);
}else{
//add a character
this.txt = fullTxt.substring(0,this.txt.length +1);
}
//insert txt into element
this.txtElement.innerHTML = `<span class="txt">${this.txt}</span>`;
// Initial Type Speed
let typeSpeed = 300;
if(this.isDeleting){
typeSpeed /= 2;
}
// If word is complete then move on to next word
if(!this.isDeleting && this.txt == fullTxt){
//make pause at the end
typeSpeed = this.wait;
//set Delete to True
this.isDeleting = true;
} else if(this.isDeleting && this.txt == ''){
this.isDeleting=false;
//move to next word
this.wordIndex ++;
// Pause before start typing
typeSpeed = 500;
}
setTimeout(() => this.type(),typeSpeed);
}
// Init on DOM Load
document.addEventListener('DOMContentLoaded',init);
//Init App
function init(){
const txtElement = document.querySelector('.txt-type');
const words = JSON.parse(txtElement.getAttribute('data-words'));
const wait = txtElement.getAttribute('data-wait');
new TypeWriter(txtElement, words, wait);
}
.hero-text p {
height: 20px;
}
<div class="agency-hero">
<section class="container">
<div class="hero-text customFadeInUp">
<h1 class="tagLine">
We create digital products
</h1>
<p><span class="txt-type " data-wait="2000" data-words='[" "," Websites "," Web Applications "]'></span></p>
<a href="agency-portfolio-4.html" class="stayPut">
See our work
</a>
</div>
</section>
</div>
Been trying to add a typewriter effect, it works great on codepen but not in WP. I've tried to add the code directly to and also to load it as a .js file from the theme's folder, I can see in page-source it's loaded, but the effect isn't executing.
I've checked in console and there's no errors, initially I got an "Uncaught TypeError: Cannot read property 'innerHTML'", so I moved it to my footer.
This is my code:
HTML
<div>
<span id="container">Testing </span> <span id="text"></span><div id="cursor"></div>
</div>
CSS
#container {
display: inline;
vertical-align: middle;
font-family: 'Poppins',Helvetica,Arial,Lucida,sans-serif!important;
font-weight: 500!important;
font-size: 45px!important;
color: #000000!important;
text-align: left!important;
line-height: 1.5em;
}
#text {
display: inline;
vertical-align: middle;
font-family: 'Poppins',Helvetica,Arial,Lucida,sans-serif!important;
font-weight: 500!important;
font-size: 45px!important;
color: #000000!important;
text-align: left!important;
height: 70px;
line-height: 1.5em;
}
#cursor {
display: inline-block;
vertical-align: middle;
width: 3px;
height: 50px;
background-color: #000000;
animation: blink .75s step-end infinite;
}
#keyframes blink {
from, to {
background-color: transparent
}
50% {
background-color: #000000;
}
}
JS
// List of sentences
var _CONTENT = [
"This",
"That",
"These",
"Those"
];
// Current sentence being processed
var _PART = 0;
// Character number of the current sentence being processed
var _PART_INDEX = 0;
// Holds the handle returned from setInterval
var _INTERVAL_VAL;
// Element that holds the text
var _ELEMENT = document.querySelector("#text");
// Cursor element
var _CURSOR = document.querySelector("#cursor");
// Implements typing effect
function Type() {
// Get substring with 1 characater added
var text = _CONTENT[_PART].substring(0, _PART_INDEX + 1);
_ELEMENT.innerHTML = text;
_PART_INDEX++;
// If full sentence has been displayed then start to delete the sentence after some time
if(text === _CONTENT[_PART]) {
// Hide the cursor
_CURSOR.style.display = 'none';
clearInterval(_INTERVAL_VAL);
setTimeout(function() {
_INTERVAL_VAL = setInterval(Delete, 50);
}, 1000);
}
}
// Implements deleting effect
function Delete() {
// Get substring with 1 characater deleted
var text = _CONTENT[_PART].substring(0, _PART_INDEX - 1);
_ELEMENT.innerHTML = text;
_PART_INDEX--;
// If sentence has been deleted then start to display the next sentence
if(text === '') {
clearInterval(_INTERVAL_VAL);
// If current sentence was last then display the first one, else move to the next
if(_PART == (_CONTENT.length - 1))
_PART = 0;
else
_PART++;
_PART_INDEX = 0;
// Start to display the next sentence after some time
setTimeout(function() {
_CURSOR.style.display = 'inline-block';
_INTERVAL_VAL = setInterval(Type, 100);
}, 200);
}
}
// Start the typing effect on load
_INTERVAL_VAL = setInterval(Type, 100);
I am working on a quiz game, and I have been having this issue for a while and I just can't figure out what I am doing wrong. Ask any question if you are confused by my explanation, i will be monitoring this post
How to recreate the problem - Type in the name displayed on the screen until you see "Game over bro!" -
Problem:
when I type in the name in the input field and click "Answer" to check if the input field value matches the name retrieved from the API, there is a variable(var attempts = 5) tracking how many times the user has attempted the question,but this variable(attempts) reduces it's value by one when the answer is correct, it should only do that when the answer is incorrect.
Also, let me know what you think about the JS code, is it bad code?
I am asking because the code in newReq function i wrote it twice, one loads and displays the data retrieved from the API when the page loads, the code inside the newReq function loads a new character when "New character" button is clicked.I was thinking about DRY the whole time, but i'm not sure how to load a new character without re-writing the code
var attemptsPara = document.querySelector("#attempts"),
attempts = 5,
scorePara = document.querySelector("#score"),
score = 0,
feedBackDiv = document.querySelector("#feedBack"),
newCharacterBtn = document.querySelector("#newCharacter"),
answerBtn = document.querySelector("#answer"),
input = document.querySelector("input");
scorePara.textContent = `Score is currently: ${score}`;
attemptsPara.textContent = `${attempts} attempts remaining`;
var feedBackText = document.createElement("p");
var characterPara = document.querySelector("#character");
//click new character button to load new character
// newCharacterBtn.addEventListener("click", () => {
// answerBtn.disabled = false;
// attempts = 5;
// attemptsPara.textContent = `${attempts} attempts remaining`;
// });
//function that displays retrieved data to the DOM
function displayCharacters(info) {
let englishName = info.attributes.name;
characterPara.textContent = `This is the character's name: ${englishName}`;
console.log(englishName, randomNumber);
}
//load new character
var randomNumber = Math.round(Math.random() * 100 + 2);
var request = new XMLHttpRequest();
request.open(
"GET",
"https://kitsu.io/api/edge/characters/" + randomNumber,
true
);
request.send();
request.onload = function() {
var data = JSON.parse(this.response);
var info = data.data;
displayCharacters(info);
//checks if the input value matches name retrieved
answerBtn.addEventListener("click", () => {
let englishName = info.attributes.name;
if (input.value === englishName) {
feedBackText.textContent = `${input.value} is correct`;
feedBackDiv.append(feedBackText);
feedBackDiv.style.backgroundColor = "green";
feedBackDiv.style.display = "block";
setTimeout(() => {
feedBackDiv.style.display = "none";
}, 3000);
score = score + 5;
scorePara.textContent = `Score is currently: ${score}`;
attempts = 5;
attemptsPara.textContent = `${attempts} attempts remaining`;
input.value = "";
newReq(); //call function to load and display new character
} else {
feedBackText.textContent = `${input.value} is wrong`;
feedBackDiv.append(feedBackText);
feedBackDiv.style.backgroundColor = "red";
feedBackDiv.style.display = "block";
input.focus();
setTimeout(() => {
feedBackDiv.style.display = "none";
}, 2000);
attempts = attempts - 1;
attemptsPara.textContent = `${attempts} attempts remaining`;
if (attempts <= 0) {
answerBtn.disabled = true;
attemptsPara.textContent = `Game over bro!`;
}
}
console.log(attempts); //check how many attempts remaining every time answerBtn is clicked
});
};
newCharacterBtn.addEventListener("click", newReq);
//function to make a new request and display it the information on the DOM,when New character button is clicked
function newReq() {
rand = randomNumber = Math.round(Math.random() * 100 + 2);
var request = new XMLHttpRequest();
request.open(
"GET",
"https://kitsu.io/api/edge/characters/" + randomNumber,
true
);
request.send();
request.onload = function() {
var data = JSON.parse(this.response);
var info = data.data;
displayCharacters(info);
answerBtn.addEventListener("click", () => {
let englishName = info.attributes.name;
if (input.value === englishName) {
feedBackText.textContent = `${input.value} is correct`;
feedBackDiv.append(feedBackText);
feedBackDiv.style.backgroundColor = "green";
feedBackDiv.style.display = "block";
//settimeout to hide feedBack div
setTimeout(() => {
feedBackDiv.style.display = "none";
}, 3000);
score = score + 5;
scorePara.textContent = `Score is currently: ${score}`;
attempts = 5;
attemptsPara.textContent = `${attempts} attempts remaining`;
input.value = "";
newReq();
} else if (input.value != englishName) {
feedBackText.textContent = `${input.value} is wrong`;
feedBackDiv.append(feedBackText);
feedBackDiv.style.backgroundColor = "red";
feedBackDiv.style.display = "block";
input.focus();
//settimeout to hide feedBack div
setTimeout(() => {
feedBackDiv.style.display = "none";
}, 2000);
attempts = attempts - 1;
attemptsPara.textContent = `${attempts} attempts remaining`;
if (attempts <= 0) {
answerBtn.disabled = true;
attemptsPara.textContent = `Game over bro!`;
}
}
});
console.log(attempts);
};
}
body {
margin: 0;
padding: 0;
background: black;
}
#imageHolder {
height: 560px;
width: 1100px;
background: #098;
margin: 10px auto;
}
#buttonHolder {
/* background: #453; */
width: 160px;
margin: 0 auto;
}
p,
h3 {
color: yellowgreen;
text-align: center;
}
h3 {
text-decoration: underline;
}
img {
width: 100%;
height: 100%;
}
button,
input {
margin: 10px 10px;
border: none;
background: #098;
display: block;
}
input {
background: white;
}
/* for the question and awnswer game */
#feedBack {
background: #098;
height: 120px;
width: 320px;
margin: 10px auto;
display: none;
}
<p id="score"></p>
<p id="character"></p>
<input type="text"> <button id="answer">Answer</button> <button id="newCharacter">New Character</button>
<p id="attempts"></p>
<div id="feedBack">
</div>
Your problem arises from calling answerBtn.addEventListener every time the answer returns - which adds more and more listeners.
Add a console log at the beginning of the click event and you'll see that after the 2nd answer the click event happens twice, then three times on the 3rd answer, etc'.
This means that on the first click the result is correct, but then on the rest of the clicks it is incorrect and must be causing the bug.
You should only listen to the event once, the variables that the event uses change and that should be sufficient. I can't fix the code for you at this time, apologies.