How do I consolidate eventlisteners? - javascript

I have an API app that lists of names that clicked on a modal pops up and shows the data.
There is another button that is connected to a second modal with more data.
The problem is that this second modal runs through all the data instead of only displaying the proper data for the name selected. I was told the reason is because I have two event listeners but I should only have 1.
Here is my codepen and here is where I was told the problem was.
https://codepen.io/drxl/pen/WNjJQXa
function addListItem(pokemon) {
let pokeUl = document.querySelector('.list-group');
let listItem = document.createElement('li');
let button = document.createElement('button');
let baseStatsButton = document.querySelector('#base-stats-button');
button.innerText = pokemon.name;
button.classList.add('btn');
button.classList.add('btn-primary');
listItem.classList.add('group-list-item');
button.setAttribute("data-target", "#my-modal");
button.setAttribute("data-toggle", "modal");
listItem.appendChild(button);
pokeUl.appendChild(listItem);
button.addEventListener('click', function() {
showDetails(pokemon);
});
baseStatsButton.addEventListener('click', function() {
showStatModal(pokemon);
});
}

Right now when you add the pokemon, you add the same click event listener to the stats button every time. That ends up executing the event handler number-of-pokemon times when clicked. There is only one button so only one event listener is needed
What we can do is to set up a global currentPokemon outside the functions. Then when you click a pokemon button, you save the pokemon and when you click base stats, show the base stats for currentPokemon
So let's move some constants out of the add function and save the pokemon clicked
const pokeUl = document.querySelector('.list-group');
const baseStatsButton = document.querySelector('#base-stats-button');
let currentPokemon; // define a reusable variable
function addListItem(pokemon) {
let listItem = document.createElement('li');
let button = document.createElement('button');
button.innerText = pokemon.name;
button.classList.add('btn');
button.classList.add('btn-primary');
listItem.classList.add('group-list-item');
button.setAttribute("data-target", "#my-modal");
button.setAttribute("data-toggle", "modal");
listItem.appendChild(button);
pokeUl.appendChild(listItem);
button.addEventListener('click', function () {
showDetails(pokemon);
currentPokemon = pokemon; // save
});
};
then use it in the stats
function showStatModal() {
if (!currentPokemon) return; // for some reason we have invoked the modal before clicking
const item = currentPokemon;
let pokemonRepository = (function () {
let pokemonList = [];
let apiUrl = 'https://pokeapi.co/api/v2/pokemon/?limit=150';
let searchInput = document.querySelector("#searchIn");
function add(pokemon) {
pokemonList.push(pokemon);
}
function getAll() {
return pokemonList;
}
const pokeUl = document.querySelector('.list-group');
const baseStatsButton = document.querySelector('#base-stats-button');
let currentPokemon;
function addListItem(pokemon) {
let listItem = document.createElement('li');
let button = document.createElement('button');
button.innerText = pokemon.name;
button.classList.add('btn');
button.classList.add('btn-primary');
listItem.classList.add('group-list-item');
button.setAttribute("data-target", "#my-modal");
button.setAttribute("data-toggle", "modal");
listItem.appendChild(button);
pokeUl.appendChild(listItem);
button.addEventListener('click', function () {
showDetails(pokemon);
currentPokemon = pokemon;
});
}
baseStatsButton.addEventListener('click', showStatModal);
function loadList() {
return fetch(apiUrl).then(function (response) {
return response.json();
}).then(function (json) {
json.results.forEach(function (item) {
let pokemon = {
name: item.name,
detailsUrl: item.url
};
add(pokemon);
});
}).catch(function (e) {
console.error(e);
})
}
function loadDetails(item) {
let url = item.detailsUrl;
return fetch(url).then(function (response) {
return response.json();
}).then(function (details) {
//Add details to item
item.imageUrl = details.sprites.front_default;
item.imageUrlBack = details.sprites.back_default;
item.height = details.height / 10;
item.weight = details.weight / 10;
// pokemon types
item.types = [];
for (var i = 0; i < details.types.length; i++) {
item.types.push(details.types[i].type.name);
}
item.types = item.types.join(', ');
//pokemon abilities
item.abilities = [];
// eslint-disable-next-line no-redeclare
for (var i = 0; i < details.abilities.length; i++) {
item.abilities.push(details.abilities[i].ability.name);
}
item.abilities = item.abilities.join(', ');
}).catch(function (e) {
console.error(e);
});
}
//loads the stats for 2nd modal
function loadStats(item) {
let url = item.detailsUrl;
return fetch(url).then(function (response) {
return response.json();
}).then(function (details) {
//add details to stats
item.stats = details.stats.map(({ base_stat, stat: { name } }) =>
`${name}: ${base_stat}`).join("<br/>")
}).catch(function (e) {
console.error(e);
});
}
function showDetails(item) {
pokemonRepository.loadDetails(item).then(function () {
// console.log("item:", item);
showModal(item);
});
}
function showModal(item) {
pokemonRepository.loadDetails(item).then(function () {
// eslint-disable-next-line no-undef
let modalBody = $(".modal-body");
// eslint-disable-next-line no-undef
let modalTitle = $(".modal-title");
//clears previous content in modal
modalTitle.empty();
modalBody.empty();
//create elenebtb for pokemon name
// eslint-disable-next-line no-undef
let nameElement = $("<h1>" + item.name + "</h1>");
//create img element
// eslint-disable-next-line no-undef
let imageElementFront = $('<img class="modal-img" style="width:50%">');
imageElementFront.attr("src", item.imageUrl);
// eslint-disable-next-line no-undef
let imageElementBack = $('<img class="modal-img" style="width:50%">');
imageElementBack.attr("src", item.imageUrlBack);
//create element for pokemon height
// eslint-disable-next-line no-undef
let heightElement = $("<p>" + "Height: " + item.height + "m</p>");
//for pokemon weight
let weightElement = $("<p>" + "Weight: " + item.weight + "kgs</p>");
//pokemon types
// eslint-disable-next-line no-undef
let typesElement = $("<p>" + "Types: " + item.types + "</p>");
//pokemon abilities
// eslint-disable-next-line no-undef
let typesAbilities = $("<p>" + "Abilities: " + item.abilities + "</p>");
//eventlistener to for search bar
searchInput.addEventListener('input', function () {
let listPokemon = document.querySelectorAll('.group-list-item');
let value = searchInput.value.toUpperCase();
listPokemon.forEach(function (pokemon) {
if (pokemon.innerText.toUpperCase().indexOf(value) > -1) {
pokemon.style.display = '';
} else {
pokemon.style.display = 'none'
}
})
});
modalTitle.append(nameElement);
modalBody.append(imageElementFront);
modalBody.append(imageElementBack);
modalBody.append(heightElement);
modalBody.append(weightElement);
modalBody.append(typesElement);
modalBody.append(typesAbilities);
// eslint-disable-next-line no-undef
$('#my-modal').modal('toggle');
});
}
function loadStatDetails(item) {
pokemonRepository.loadStats(item).then(function () {
showStatModal(item);
});
}
function showStatModal() {
if (!currentPokemon) return; // for some reason we have invoked the modal before clicking
const item = currentPokemon;
pokemonRepository.loadStats(item).then(function () {
// eslint-disable-next-line no-undef
let StatmodalBody = $(".Statmodal-body");
// eslint-disable-next-line no-undef
let StatmodalTitle = $(".Statmodal-title");
//clears previous content in modal
StatmodalTitle.empty();
StatmodalBody.empty();
//create elenebtb for pokemon name
// eslint-disable-next-line no-undef
let nameElement = $("<h1>" + item.name + "</h1>");
//add stats
let statsElement = $("<p>" + item.stats + "<p>");
StatmodalTitle.append(nameElement);
StatmodalBody.append(statsElement);
$('#my-Statmodal').modal('show');
});
}
return {
add: add,
getAll: getAll,
addListItem: addListItem,
loadList: loadList,
loadDetails: loadDetails,
showDetails: showDetails,
loadStats: loadStats,
loadStatDetails: loadStatDetails,
};
})();
pokemonRepository.loadList().then(function () {
pokemonRepository.getAll().forEach(function (pokemon) {
pokemonRepository.addListItem(pokemon);
});
});
let link = document.getElementById("back-to-top");
var amountScrolled = 250;
//makes button show
window.addEventListener('scroll', function (e) {
if (this.window.pageYOffset > amountScrolled) {
link.classList.add('show');
} else {
link.className = 'back-to-top';
}
});
//scrolls to top
link.addEventListener('click', function (e) {
e.preventDefault();
var distance = 0 - window.pageYOffset;
var increments = distance / (500 / 16);
function animateScroll() {
window.scrollBy(0, increments);
if (window.pageYOffset <= document.body.offsetTop) {
clearInterval(runAnimation);
}
};
// Loop the animation function
var runAnimation = setInterval(animateScroll, 16);
});
/*
* Prefixed by https://autoprefixer.github.io
* PostCSS: v7.0.29,
* Autoprefixer: v9.7.6
* Browsers: last 4 version
*/
* {
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
body {
background-color: rgb(3, 136, 180);
}
.navbar {
background-color: #ffcb05!important;
}
.navbar-brand {
color: #3d7dca!important;
}
.navbar-logo {
width: 42px;
height: 42px;
margin-left: 1rem;
}
.modal-header {
border-bottom: 1px solid black;
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
}
.modal-content {
background-color: #ffcb05!important;
border: #3d7dca solid 6px!important;
text-align: center;
}
.modal-close {
background-color: #ee1515;
border: white solid 2px;
color: white;
padding: 8.5px 16.5px;
border-radius: 50%;
font-size: 1.25rem;
}
.modal-close:active {
border-color: #ee1515;
background-color: white;
color: #ee1515;
}
.modal-title h1 {
margin-bottom: 0;
}
.modal h1 {
margin-top: 0;
text-align: left;
font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
text-transform: uppercase;
font-size: 3rem;
color: #3d7dca;
margin-left: 2rem;
}
.modal p {
margin-bottom: 0;
text-align: left;
font-weight: 700;
font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
font-size: 1.5rem;
color: #3d7dca;
text-transform: capitalize;
}
.list-group {
list-style-type: none;
text-align: center;
-webkit-padding-start: 0;
padding-inline-start: 0;
margin: 2rem;
text-transform: capitalize;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
}
.list-group li {
width: auto;
margin: 1rem;
}
.Statmodal-body p {
margin-left: 3%;
}
.btn {
/*👉 background-color: #cc1313;👈*/
background-color: #db0606;
border: black solid 2px;
color: white;
text-transform: capitalize;
padding: 24px 72px;
width: 300px;
}
.btn:hover {
background-color: white;
border: black solid 2px;
color: #ee1515;
}
.btn-outline-success:hover {
background-color: white;
color: #ee1515;
border: black 2px solid;
}
.btn-primary {
font-size: 1.5rem;
}
.btn-primary:hover {
color: #ee1515;
background-color: white;
}
#search-button {
background-color: #db0606;
border: black solid 2px;
color: white;
padding: 4px 25px;
}
.modal-footer {
justify-content: center;
border-top: none;
}
.modal-button {
text-align: center;
width: auto;
}
.back-to-top {
background-color: #ee1515;
color: #FFFFFF;
opacity: 0;
transition: opacity .6s ease-in-out;
z-index: 999;
position: fixed;
right: 20px;
bottom: 20px;
width: 50px;
height: 50px;
box-sizing: border-box;
border-radius: 0%;
}
a.back-to-top {
font-weight: 1000;
letter-spacing: 2px;
font-size: 14px;
text-transform: uppercase;
text-align: center;
line-height: 1.6;
padding-left: 2px;
padding-top: 14px;
text-decoration: none;
}
.back-to-top:hover,
.back-to-top:focus,
.back-to-top:visited {
color: #FFFFFF;
}
.back-to-top.show {
opacity: 1;
}
#media screen and (max-width: 727px) {
.btn {
width: 250px;
}
}
#media screen and (max-width:627px) {
.btn {
padding: 10px 10px;
width: 200px;
font-size: 1.15rem;
}
}
#media screen and (max-width:575px) {
.justify-content-between {
-webkit-box-pack: center!important;
-ms-flex-pack: center!important;
justify-content: center!important;
}
.form-inline {
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
}
#search-button {
margin-top: .5rem;
padding: 4px 42px;
}
.modal p {
font-size: 1.5rem!important;
}
}
#media screen and (max-width:500px) {
.modal p {
font-size: 1.5rem!important;
}
}
#media screen and (max-width: 493px) {
.justify-content-between {
-webkit-box-pack: center!important;
-ms-flex-pack: center!important;
justify-content: center!important;
}
}
#media screen and (max-width:450px) {
.modal-header {
-webkit-box-align: center!important;
-ms-flex-align: center!important;
align-items: center!important;
}
.modal-title h1 {
font-size: 1.75rem;
}
button {
font-size: .85rem;
}
.modal p {
font-size: 1rem!important;
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PokemonAPI</title>
<link href="img/Poke_Ball.png" rel="shortcut icon" type="image/x-icon" />
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.3/css/all.css" integrity="sha384-SZXxX4whJ79/gErwcOYf+zWLeJdY/qpuqC4cAa9rOGUstPomtqpuNWT9wdPEn2fk" crossorigin="anonymous">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link href="css/styles.css" rel="stylesheet">
</head>
<body>
<!--Nav-->
<nav class="navbar navbar-light bg-light justify-content-between">
<a class="navbar-brand">PokemonAPI<img class="navbar-logo" src="img/Poke_Ball.png"></a>
<form class="form-inline">
<input id="searchIn" class="form-control mr-sm-2" type="search" placeholder="Search for a Pokemon" aria-label="Search for Pokemon">
<button id="search-button" type="submit">Search</button>
</form>
</nav>
<!--list of pokemon-->
<ul class="list-group"></ul>
<div class="modal fade" id="my-modal" aria-hidden="true" tabindex="-1" role="dialog" aria-labelledby="pokemonModalLabel">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" aria-labelledby="pokemonModalLabel">Modal 1 title</h5>
<button type="button" class="modal-close" data-bs-dismiss="modal" aria-label="Close">X</button>
</div>
<div class="modal-body">
</div>
<div class="modal-footer">
<button id="base-stats-button" class="btn modal-button" data-bs-target="#my-Statmodal" data-bs-toggle="modal" data-bs-dismiss="modal">Base Stats</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="my-Statmodal" aria-hidden="true" aria-labelledby="pokemonStatModalLabel" tabindex="-1" role="dialog">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="Statmodal-title" id="exampleModalToggleLabel2" aria-labelledby="pokemonStatModalLabel">Modal 2 title</h5>
<button type="button" class="modal-close" data-bs-dismiss="modal" aria-label="Close">X</button>
</div>
<div class="Statmodal-body">
</div>
<div class="modal-footer">
<button class="btn modal-button" data-bs-target="#my-modal" data-bs-toggle="modal" data-bs-dismiss="modal">Back to Pokemon</button>
</div>
</div>
</div>
</div>
<!--Top of Page Button-->
<i class="fas fa-arrow-up fa-2x"></i>
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.5.1.js" integrity="sha256-QWo7LDvxbWT2tbbQ97B53yJnYU3WhH/C8ycbRAkjPDc=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<!--PolyFills-->
<script src="js/promise-polyfill.js"></script>
<script src="js/fetch-polyfill.js"></script>
<!--JS-->
<script src="js/scripts.js"></script>
</body>
</html>

Related

pokemonApi filter function

Hey everyone I'm trying to use the filter method on the Pokémon api data, but I'm not understanding what I'm doing wrong. What I'm trying to accomplish is when a user clicks the filter buttons, the type of Pokémon is displayed.. for example..(user clicks fire button, fire Pokémon are displayed),
I tried to use the .pokeContainer div to display when user clicks the button but that's not working, any help would be greatly appreciated. Thank you
//Function to fetch 151 pokemon
function fetchPokemon() {
let pokemonArr = [];
// const url = `https://pokeapi.co/api/v2/pokemon?${i}`
// const url = 'Charizard, Mewtwo';
for (let i = 1; i <= 150; i++) {
const url = `https://pokeapi.co/api/v2/pokemon/${i}`
pokemonArr.push(fetch(url).then(data => data.json()))
// pokemonArr.push(url);
}
// console.log(pokemonArr)
//Promise.all to iterate over array and return a single array/Promise
// Promise.all(pokemonArr).then(data => console.log(data))
Promise.all(pokemonArr).then((results) => {
const pokemon = results.map((result) => ({
name: result.name,
id: result.id,
image: result.sprites['front_default'],
type: result.types.map((type) => type.type.name),
}))
createPokeCard(pokemon)
})
}
//Create pokemon div and display pokemon
const createPokeCard = (pokemon) => {
const allPokemonContainer = document.querySelector('.allPokemonContainer');
const pokemonInnerHTML = pokemon.map((pokemon) =>
`<div class="pokeContainer">
<image src="${pokemon.image}">
<h2 class="card-title">${pokemon.id}. ${pokemon.name}</h2>
<p>${pokemon.type}</p>
</div>`
)
allPokemonContainer.innerHTML = pokemonInnerHTML;
// typeFilter(pokemon)
searchFilter()
typeFilter(pokemon)
}
//Search filter for displaying Pokemon
const searchFilter = () => {
const searchBar = document.querySelector('.search-bar');
const pokeContainer = document.querySelectorAll('.pokeContainer');
searchBar.addEventListener('keyup', (event) => {
let value = event.target.value.toLowerCase();
console.log(value)
pokeContainer.forEach((container) => {
if (container.querySelector('h2').textContent.toLowerCase().includes(value)) {
container.style.display = 'block'
} else {
container.style.display = 'none'
}
})
// console.log('Hello')
// console.log(search);
})
}
// a filter for iterating through pokemon types
function typeFilter(pokemon) {
const pokeContainer = document.querySelectorAll('.pokeContainer');
const filterBtn = document.querySelectorAll('.filter-btn');
filterBtn.forEach(function(btn) {
btn.addEventListener('click', (e) => {
// console.log(btn);
// console.log(btn.innerText);
let type = e.currentTarget.innerText;
console.log(type);
// console.log(pokemon)
pokeContainer.forEach((container) => {
if (type === btn.innerText) {
// console.log(type)
container.style.display = 'block'
} else {
container.style.display = 'none'
}
})
let pokemonFilter = pokemon.filter((item) => {
// console.log(type);
if (item === item.type) {
// console.log(item);
return item;
}
})
// pokemon.filter((item) => {
// if (item === type) {
// return item;
// }
// })
// console.log(pokemon)
// console.log(pokemonFilter);
})
});
// console.log(pokemon);
}
// typeFilter();
fetchPokemon();
body {
// background-color: green;
margin: 0;
padding: 0;
}
// .logo {
// width: 50%
// }
// main {
// // background-image: url('imgs/pokeballcollection1.jpg');
// background-image: url('imgs/pokeballcollection2.jpg');
// }
.pokeContainer {
border: 2px solid black;
margin: 1rem;
background-color: rgb(255, 255, 255);
}
.flex-container {
display: flex;
// justify-content: space-between;
// justify-content: center;
text-align: center;
justify-content: space-around;
}
img {
// width: 100px;
width: 100%;
}
h3 {
text-align: center;
}
p {
width: 50px;
border: 1px solid black;
border-radius: 11px;
text-align: center;
background-color: rgb(95, 158, 160);
color: rgb(255, 255, 255);
}
ul {
padding: 0.5rem;
text-transform: uppercase;
}
ul.menu {
display: flex;
text-align: center;
}
li.list {
color: black;
}
li {
// border: 2px solid black;
width: 100px;
border-radius: 5px;
padding: 0.1rem;
list-style: none;
margin: 0.5rem;
// font-size: small;
font-size: 10px;
font-weight: 600;
// color: white;
}
.search {
display: flex;
justify-content: center;
}
.sort-method {
display: flex;
justify-content: center;
margin: 2rem 0;
button.sortAscending {
margin-right: 10px;
}
}
// .search {
// display: flex;
// justify-content: center;
// .search-bar {
// // background: url('../imgs/search-icon3.png') no-repeat 7px;
// background-size: 18px;
// // background-image: url('dist/css/pokemon-logo-png (1).png');
// }
// }
<!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 rel="stylesheet" href="dist/css/styles.css">
<title>Pokemon API</title>
</head>
<body>
<main>
<header>
<nav>
<div class="navContainer">
<div class="logo">
<img src="imgs/pokemon-logo-png (1).png" alt="">
</div>
<div class="navLinks">
<ul class="menu">
<li class="list"><a>Home</a></li>
<li class="list"><a>Archive</a></li>
<li class="list"><a>Pokemon</a></li>
<li class="list"><a>About</a></li>
</ul>
</div>
</div>
</nav>
</header>
<div class="search">
<input type="text" class="search-bar" name="search" placeholder="search pokemon">
</div>
<div class="filterByType">
<button class="filter-btn">Grass</button>
<button class="filter-btn">Fire</button>
<button class="filter-btn">Water</button>
<button class="filter-btn">Electric</button>
<button class="filter-btn">Psyhcic</button>
<button class="filter-btn">Ground</button>
<button class="filter-btn">Steel</button>
<button class="filter-btn">Flying</button>
<button class="filter-btn">Bug</button>
<button class="filter-btn">Rock</button>
<button class="filter-btn">Fairy</button>
<button class="filter-btn">Dark</button>
</div>
<div class="sort-method">
<select name="drop-down" id="drop-down">
<option value="ID ASC">ID ASC</option>
<option value="ID DSC">ID DSC</option>
</select>
<!-- <button class="sortAscending">Sort A-Z</button>
<button class="sortDecending">Sort Z-A</button> -->
</div>
<div class="allPokemonContainer">
</div>
</main>
<script src="app4.js"></script>
</body>
</html>

How to clear the innerHTML content of an HTMLCollection?

I am building a Tic Tac Toe game. My problem is being unable to clear the innerHTML of multiple divs (div class .cell) when clicking the resetButton. The divs I am trying to clear are the children of gameDisplay.
When I tried to locate the cell elements in my chrome dev tool, I get an HTML Collection:
ScreenShot Here
I am having difficulty accessing the innerHTML of these elements. I currently have my resetGame() function returning gameDisplay.innerHTML=""(line 79) however, that removes all of the content of the parent element and removes my grid(board) completely. I know that is not specific enough. As a result, I tried returning gameDisplay.children.innerHTML =""; instead but nothing happens in the DOM or in my chrome dev tools console.
How can I access the innerHTML of these divs and remove the HTML inside of them using the resetGame function?
JS
const playerFactory = () => {
const playTurn = (event, currentPlayer) => {
const id = boardObject.cells.indexOf(event.target);
boardObject.boardDisplay[id] = currentPlayer;
boardObject.render();
};
return {
playTurn
};
};
// Gameboard object
const boardObject = (() => {
let boardDisplay = ['', '', '', '', '', '', '', '', ''];
const cells = Array.from(document.querySelectorAll(".cell"));
// displays the content of the boardArray
const render = () => {
boardDisplay.forEach((cells, idx) => {
cells[idx].innerHTML = boardDisplay[idx];
});
};
return {
boardDisplay,
render,
cells
};
})();
// Create Array from DOM
const boardArray = [...document.querySelectorAll(".cell")].map(cells=>cells.innerHTML);
console.log(boardArray);
// Display controller, shows which player is which
const displayController = (() => {
const playerOne = 'X';
const playerTwo = 'O';
const gameBoard = document.querySelector(".game-board");
let currentPlayer = playerOne;
const switchPlayer = () => {
currentPlayer = currentPlayer === playerOne ? playerTwo : playerOne;
}
gameBoard.addEventListener("click", (event) => {
if (event.target.classList.contains("cell")) {
if (event.target.textContent === "") {
event.target.textContent = currentPlayer;
const indexOfClickedCell = Array.prototype.indexOf.call(document.querySelectorAll('.cell'), event.target);
boardArray[indexOfClickedCell] = currentPlayer;
switchPlayer();
}
}
});
})();
const gameDisplay = document.getElementById("game-board");
// Define reset button function first
function resetGame(){
// Define result, reset the board
//if the board array index is equal to a character
if(boardArray.includes('X','O')){
{ return gameDisplay.innerHTML = "";
}
}
else
{;}
//and if the cells textContent is equal to a character
//return the result, which is the reset board function
}
const resetButton = document.querySelector(".restart");
resetButton.addEventListener("click", (event) => {
resetGame(event);
});
HTML
<!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 rel="stylesheet" href="https://use.typekit.net/xtq6hun.css">
<link rel="stylesheet" href="styles.css">
<title>TIC-TAC-TOE</title>
</head>
<body>
<main>
<div class="top-text">
<h1>TIC TAC TOE</h1>
<div id ="game-text">
</div>
</div>
<div class="game-board" id="game-board">
<div class="cell" id="cell-1"></div>
<div class="cell" id="cell-2"></div>
<div class="cell" id="cell-3"></div>
<div class="cell" id="cell-4"></div>
<div class="cell" id="cell-5"></div>
<div class="cell" id="cell-6"></div>
<div class="cell" id="cell-7"></div>
<div class="cell" id="cell-8"></div>
<div class="cell" id="cell-9"></div>
</div>
<div class="bottom-box">
<button class="restart">RESTART GAME</button>
</div>
</main>
<script src="script.js" defer></script>
</body>
</html>
CSS
/*CSS RESET*/
* {
margin:0;
padding:0;
}
body {
background-color: #faf9fa;
}
main {
display: flex;
align-content: center;
flex-direction: column;
flex-wrap: nowrap;
justify-content: center;
align-items: center;
}
h1 {
font-family: blackest-text, sans-serif;
font-weight: 700;
font-style: normal;
font-size: 60px;
color: #38393a;
}
h2 {
font-family: elza, sans-serif;
font-weight: 500;
font-style: normal;
font-size: 20px;
color: #38393a;
}
.top-text {
border: none;
margin: 0.5em;
display: flex;
flex-direction: column;
align-items: center;
}
.game-board {
display: grid;
grid-template-columns: repeat(3, 1fr);
border: 1px solid #38393a;
margin: 0px auto;
background-color: #faf9fa;
margin-bottom: 2em;
}
.cell:hover {
background-color: rgb(221, 253, 228);
}
.cell {
/*STYLING X'S AND O'S*/
font-family: blackest-text, sans-serif;
font-weight: 700;
font-style: normal;
font-size: 100px;
color: #38393a;
/*STYLING INDIVUAL CELLS*/
min-height: 170px;
min-width: 170px;
border: 1px solid #38393a;
display: flex;
align-content: center;
flex-wrap: nowrap;
justify-content: center;
align-items: center;
}
img {
width: 90px;
color:#38393a;
}
.message-display {
border: none;
margin-bottom: 1em;
}
.restart{
font-family: elza, sans-serif;
font-weight: 500;
font-style: normal;
height: 3.5em;
width: 12em;
border-radius: 30px;
border: 1px solid #38393a;
background-color: #faf9fa;
padding: 1px;
}
.restart:hover{
background-color: rgb(221, 253, 228);
}
span {
font-family: blackest-text, sans-serif;
font-weight: 700;
font-style: normal;
font-size: 100px;
color: #38393a;
}
p {
font-family: blackest-text, sans-serif;
font-weight: 700;
font-style: normal;
font-size: 30px;
color: #38393a;
}
In your resetGame function you should loop over all the cells and remove their content instead of deleting the whole game-board content.
for(let i =0;i < gameDisplay.children.length;i++) {
gameDisplay.children[i].innerHTML = '';
}

Range slider resetting to default on firefox

So I'm making an Etch-a-Sketch with a range slider to change the grid size, but the slider keeps resetting to its default size (16x16) as soon as I move the mouse after changing the value (if I change the value and don't move the mouse, the size doesn't reset). For some reason this doesn't happen on Chrome: the value and grid size both change and stay that way until I change them again.
Here's the JSFiddle: https://jsfiddle.net/CamiCoding/7zpt14cs/
HTML:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Etch-a-Sketch</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body>
<div class="title">
<h1>Etch-a-Sketch</h1>
</div>
<div class="btns">
<button id="blackBtn">Black</button>
<button id="rainbowBtn">Rainbow</button>
<button id="colorBtn">Color</button>
<button id="eraserBtn">Eraser</button>
<button id="resetBtn">Reset</button>
<div class="colorPicker">
<input type="color" id="color" value="#000000">
<span>Pick a color</span>
</div>
<div class="sliderAndValue">
<input type="range" min="2" max="100" value="16" id="slider">
<p class="value">16</p>
</div>
</div>
<div class="grid">
</div>
<script src="script.js" defer></script>
</body>
</html>
CSS:
#font-face {
src: url("../fonts/sf-atarian-system.regular.ttf");
font-family: Atarian;
}
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-family: Atarian;
background-color: #D1D1D1;
}
.title h1 {
font-size: 80px;
margin: 0px;
}
.btns {
display: flex;
flex-direction: row;
width: 700px;
height: 50px;
justify-content: space-evenly;
margin-top: 20px;
margin-bottom: 20px;
}
.btns button {
font-family: Atarian;
font-size: 24px;
height: 40px;
width: 80px;
border-radius: 5px;
transition: transform 0.2s ease-in-out;
}
.btns button:hover {
transform: scale(1.2);
}
.btns .active {
background-color: #505050;
color: white;
}
.colorPicker {
display: flex;
flex-direction: column;
align-items: center;
font-size: 20px;
}
.color span {
text-align: center;
}
#color {
width: 90px;
}
.sliderAndValue {
-webkit-appearance: none;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
font-size: 24px;
}
#slider {
-webkit-appearance: none;
width: 150px;
height: 10px;
background: #000;
outline: none;
border: 4px solid gray;
border-radius: 4px;
}
/* for firefox */
#slider::-moz-range-thumb {
width: 5px;
height: 20px;
background: #000;
cursor: pointer;
border: 4px solid gray;
border-radius: 4px;
}
/* for chrome/safari */
#slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 5px;
height: 20px;
background: #000;
cursor: pointer;
border: 4px solid gray;
border-radius: 4px;
}
.cell {
border: 1px solid black;
}
.cell.active {
background-color: black;
}
.grid {
display: inline-grid;
grid-template-columns: repeat(16, 2fr);
grid-template-rows: repeat(16, 2fr);
border: 5px solid gray;
border-radius: 5px;
height: 700px;
width: 700px;
background-color: white;
}
JS:
const grid = document.querySelector('.grid');
const resetBtn = document.getElementById('resetBtn');
const eraserBtn = document.getElementById('eraserBtn');
const blackBtn = document.getElementById('blackBtn');
const colorBtn = document.getElementById('colorBtn')
const colorValue = document.getElementById('color');
const slider = document.getElementById('slider');
const sliderValue = document.querySelector('.value');
const DEFAULT_COLOR = '#000000';
const DEFAULT_MODE = 'black';
const DEFAULT_SIZE = 16;
let currentColor = DEFAULT_COLOR;
let currentMode = DEFAULT_MODE;
let mouseDown = false
document.body.onmousedown = () => (mouseDown = true)
document.body.onmouseup = () => (mouseDown = false)
function setCurrentColor(newColor) {
currentColor = newColor;
}
function setCurrentMode(newMode) {
activateButton(newMode);
currentMode = newMode;
}
blackBtn.onclick = () => setCurrentMode('black');
rainbowBtn.onclick = () => setCurrentMode('rainbow');
eraserBtn.onclick = () => setCurrentMode('eraser');
colorBtn.onclick = () => setCurrentMode('color');
resetBtn.onclick = () => createGrid();
function createGrid() {
removeCells(grid);
let val = document.getElementById('slider').value;
sliderValue.textContent = val;
grid.style.gridTemplateColumns = (`repeat(${val}, 2fr`);
grid.style.gridTemplateRows = (`repeat(${val}, 2fr`);
for(let i = 0; i < val * val; i++) {
const cell = document.createElement('div');
cell.classList.add('cell');
cell.addEventListener('mouseover', changeColor);
cell.addEventListener('mousedown', changeColor);
grid.appendChild(cell);
}
}
function activateButton(newMode) {
if (currentMode === 'rainbow') {
rainbowBtn.classList.remove('active');
} else if (currentMode === 'color') {
colorBtn.classList.remove('active');
} else if (currentMode === 'eraser') {
eraserBtn.classList.remove('active');
} else if (currentMode === 'black') {
blackBtn.classList.remove('active');
}
if (newMode === 'rainbow') {
rainbowBtn.classList.add('active');
} else if (newMode === 'color') {
colorBtn.classList.add('active');
} else if (newMode === 'eraser') {
eraserBtn.classList.add('active');
} else if (newMode === 'black') {
blackBtn.classList.add('active');
}
}
function changeColor(e) {
if (e.type === 'mouseover' && !mouseDown) return;
if (currentMode === 'rainbow') {
const randomR = Math.floor(Math.random() * 256);
const randomG = Math.floor(Math.random() * 256);
const randomB = Math.floor(Math.random() * 256);
e.target.style.backgroundColor = `rgb(${randomR}, ${randomG}, ${randomB})`;
} else if (currentMode === 'color') {
e.target.style.backgroundColor = colorValue.value;
} else if (currentMode === 'eraser') {
e.target.style.backgroundColor = '#ffffff';
} else if (currentMode === 'black') {
e.target.style.background = '#000000';
}
}
slider.addEventListener('input', function(){
let val = document.getElementById('slider').value;
sliderValue.textContent = val;
removeCells(grid);
grid.style.gridTemplateColumns = (`repeat(${val}, 2fr`);
grid.style.gridTemplateRows = (`repeat(${val}, 2fr`);
for (let i = 0; i < val * val; i++) {
const cell = document.createElement('div');
cell.classList.add('cell');
cell.addEventListener('mouseover', changeColor);
cell.addEventListener('mousedown', changeColor);
grid.appendChild(cell);
}
});
function removeCells(parent){
while(grid.firstChild){
grid.removeChild(grid.firstChild);
}
}
window.onload = () => {
createGrid(DEFAULT_SIZE);
activateButton(DEFAULT_MODE);
}
I do have another question regarding this same project, but about a different issue, please let me know if I should post a different question for it or if I should post it here too!
Thanks a lot in advance :).
I was able to track down the source of the issue, and fix it. Sounds weird, but document.body.onmousedown and document.body.onmouseup were creating the issue.
Replacing them with addEventListener seems to fix it.
I also removed some repeated code (in slider's input listener), by making maximum use of createGrid() function.
const grid = document.querySelector('.grid');
const resetBtn = document.getElementById('resetBtn');
const eraserBtn = document.getElementById('eraserBtn');
const blackBtn = document.getElementById('blackBtn');
const colorBtn = document.getElementById('colorBtn')
const colorValue = document.getElementById('color');
const slider = document.querySelector('#slider');
const sliderValue = document.querySelector('.value');
const DEFAULT_COLOR = '#000000';
const DEFAULT_MODE = 'black';
const DEFAULT_SIZE = 16;
let currentColor = DEFAULT_COLOR;
let currentMode = DEFAULT_MODE;
let mouseDown = false
document.body.addEventListener("mousedown", () => (mouseDown = true))
document.body.addEventListener("mouseup", () => (mouseDown = false))
function setCurrentColor(newColor) {
currentColor = newColor;
}
function setCurrentMode(newMode) {
activateButton(newMode);
currentMode = newMode;
}
blackBtn.onclick = () => setCurrentMode('black');
rainbowBtn.onclick = () => setCurrentMode('rainbow');
eraserBtn.onclick = () => setCurrentMode('eraser');
colorBtn.onclick = () => setCurrentMode('color');
resetBtn.onclick = () => createGrid();
function createGrid(val = 16) {
slider.value = val;
sliderValue.textContent = val;
removeCells(grid);
grid.style.gridTemplateColumns = (`repeat(${val}, 2fr`);
grid.style.gridTemplateRows = (`repeat(${val}, 2fr`);
for (let i = 0; i < val * val; i++) {
const cell = document.createElement('div');
cell.classList.add('cell');
cell.addEventListener('mouseover', changeColor);
cell.addEventListener('mousedown', changeColor);
grid.appendChild(cell);
}
}
function activateButton(newMode) {
if (currentMode === 'rainbow') {
rainbowBtn.classList.remove('active');
} else if (currentMode === 'color') {
colorBtn.classList.remove('active');
} else if (currentMode === 'eraser') {
eraserBtn.classList.remove('active');
} else if (currentMode === 'black') {
blackBtn.classList.remove('active');
}
if (newMode === 'rainbow') {
rainbowBtn.classList.add('active');
} else if (newMode === 'color') {
colorBtn.classList.add('active');
} else if (newMode === 'eraser') {
eraserBtn.classList.add('active');
} else if (newMode === 'black') {
blackBtn.classList.add('active');
}
}
function changeColor(e) {
if (e.type === 'mouseover' && !mouseDown) return;
if (currentMode === 'rainbow') {
const randomR = Math.floor(Math.random() * 256);
const randomG = Math.floor(Math.random() * 256);
const randomB = Math.floor(Math.random() * 256);
e.target.style.backgroundColor = `rgb(${randomR}, ${randomG}, ${randomB})`;
} else if (currentMode === 'color') {
e.target.style.backgroundColor = colorValue.value;
} else if (currentMode === 'eraser') {
e.target.style.backgroundColor = '#ffffff';
} else if (currentMode === 'black') {
e.target.style.background = '#000000';
}
}
slider.addEventListener('input', function(e) {
let val = parseInt(document.getElementById('slider').value);
createGrid(val);
});
function removeCells(parent) {
while (grid.firstChild) {
grid.removeChild(grid.firstChild);
}
}
window.onload = () => {
createGrid(DEFAULT_SIZE);
activateButton(DEFAULT_MODE);
}
#font-face {
src: url("../fonts/sf-atarian-system.regular.ttf");
font-family: Atarian;
}
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-family: Atarian;
background-color: #D1D1D1;
}
.title h1 {
font-size: 80px;
margin: 0px;
}
.btns {
display: flex;
flex-direction: row;
width: 700px;
height: 50px;
justify-content: space-evenly;
margin-top: 20px;
margin-bottom: 20px;
}
.btns button {
font-family: Atarian;
font-size: 24px;
height: 40px;
width: 80px;
border-radius: 5px;
transition: transform 0.2s ease-in-out;
}
.btns button:hover {
transform: scale(1.2);
}
.btns .active {
background-color: #505050;
color: white;
}
.colorPicker {
display: flex;
flex-direction: column;
align-items: center;
font-size: 20px;
}
.color span {
text-align: center;
}
#color {
width: 90px;
}
.sliderAndValue {
-webkit-appearance: none;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
font-size: 24px;
}
#slider {
-webkit-appearance: none;
width: 150px;
height: 10px;
background: #000;
outline: none;
border: 4px solid gray;
border-radius: 4px;
}
/* for firefox */
#slider::-moz-range-thumb {
width: 5px;
height: 20px;
background: #000;
cursor: pointer;
border: 4px solid gray;
border-radius: 4px;
}
/* for chrome/safari */
#slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 5px;
height: 20px;
background: #000;
cursor: pointer;
border: 4px solid gray;
border-radius: 4px;
}
.cell {
border: 1px solid black;
}
.cell.active {
background-color: black;
}
.grid {
display: inline-grid;
grid-template-columns: repeat(16, 2fr);
grid-template-rows: repeat(16, 2fr);
border: 5px solid gray;
border-radius: 5px;
height: 700px;
width: 700px;
background-color: white;
user-select: none;
}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Etch-a-Sketch</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body>
<div class="title">
<h1>Etch-a-Sketch</h1>
</div>
<div class="btns">
<button id="blackBtn">Black</button>
<button id="rainbowBtn">Rainbow</button>
<button id="colorBtn">Color</button>
<button id="eraserBtn">Eraser</button>
<button id="resetBtn">Reset</button>
<div class="colorPicker">
<input type="color" id="color" value="#000000">
<span>Pick a color</span>
</div>
<div class="sliderAndValue">
<input type="range" min="2" value="16" id="slider">
<p class="value">16</p>
</div>
</div>
<div class="grid">
</div>
<script src="script.js" defer></script>
</body>
</html>

CSS - Preventing element from being push upwards during DOM manipulation

I have a todo list built with vanilla JavaScript.
As you'd expect, when you click the add button new todos appear on the DOM.
The problem is that the todo list gets pushed upwards each time you add a new todo and eventually it overwrites the navbar and leaves the viewport.
I've tried adding all the possible CSS position properties to the title but the list still keeps moving regardless
For the code and visuals - https://codepen.io/greevesh/pen/gOxNEPy
This is the element I want to prevent from moving -
<div class="d-flex justify-content-center mb-3" style="position: sticky;">
<img class="logo" src="/img/planning.svg" alt="tasktracker-logo">
</div>
You could allow the list to grow but stop moving up when reaching the top by adding margin: auto to your sub-container. See this answer for more details
.sub-container {
margin: auto;
display: block;
}
That is because you make the .container align-items: center;. Please remove that and add padding-top or margin-top as you want. Putting align-items: center; is a bad practice for element that has dynamic height.
const toDoList = document.getElementById("toDoList");
const title = document.getElementById("title");
const toDoContainer = document.getElementById("toDoContainer");
const allCheckboxes = toDoContainer.getElementsByClassName("checkbox");
const allXBtns = toDoContainer.getElementsByClassName("X");
const saveBtn = document.getElementById("save");
const clearBtn = document.getElementById("clear");
const saveMsg = document.getElementById("saveMsg");
const saveTitle = () => {
localStorage.setItem(activeEmail.innerHTML + " Title", JSON.stringify(title.value));
};
function createToDo() {
const createdToDoContainer = document.createElement("div");
createdToDoContainer.id = 'toDo' + new Date().getTime(); // unique ID
const createdCheckbox = document.createElement("INPUT");
createdCheckbox.setAttribute("type", "checkbox");
const createdToDo = document.createElement("INPUT");
const createdXBtn = document.createElement("SPAN");
createdToDoContainer.appendChild(createdCheckbox);
createdToDoContainer.appendChild(createdToDo);
createdToDoContainer.appendChild(createdXBtn);
createdToDoContainer.classList.add("toDoInnerContainer");
createdCheckbox.classList.add("checkbox");
createdToDo.classList.add("input");
createdXBtn.classList.add("X");
createdXBtn.innerHTML = "X";
toDoContainer.appendChild(createdToDoContainer);
}
let checkedToDos = [];
// delete button functionality
toDoContainer.addEventListener("click", (e) => {
const tgt = e.target;
if (tgt.classList.contains("X")) {
const parent = tgt.parentElement;
parent.remove();
const toDoValue = parent.querySelector(".input").value;
if (parent.querySelector(".checkbox").checked) {
if (checkedToDos.includes(toDoValue)) {
checkedToDos = checkedToDos.filter(val => val !== toDoValue);
}
}
}
});
const saveToDos = () => {
// change the global todo object (and make it an array)
toDos = [...document.querySelectorAll(".input")].map(toDo => {
const checked = toDo.parentNode.querySelector(".checkbox").checked;
const id = toDo.closest("div").id;
const val = toDo.value;
if (toDo.parentNode.querySelector(".checkbox").checked == true) {
checkedToDos.push(val);
}
return { id, val, checked }
});
localStorage.setItem((activeEmail.innerHTML), JSON.stringify(toDos));
};
// changes todo styling depending on checkbox state (checked or not checked)
toDoContainer.addEventListener("change", (e) => {
const tgt = e.target;
const chk = tgt.checked;
const toDo = tgt.parentNode.querySelector('.input');
toDo.style.textDecoration = chk ? "line-through" : "none";
toDo.style.opacity = chk && toDo.value !== "" ? "50%" : "100%";
});
document.getElementById("add").addEventListener("click", createToDo);
saveBtn.addEventListener("click", () => {
saveTitle();
saveToDos();
saveMsg.innerHTML = "Your tasks have been saved.";
});
// makes sure save message disappears once user clicks elsewhere
window.addEventListener("click", (e) => {
const tgt = e.target;
if (saveMsg.innerHTML !== "" && saveBtn !== tgt) {
saveMsg.innerHTML = "";
}
})
const allToDos = toDoContainer.getElementsByClassName("input");
const clearToDosAndTitle = () => {
title.value = "";
checkedToDos.splice(0, checkedToDos.length);
[...document.getElementsByClassName("toDoInnerContainer")].map(toDo => {
// remove all todos but leave at least one empty one on the DOM
// the length of the checkbox collection matters most because it's the first part of the todo to be loaded onto the DOM
while (toDo.lastChild && allCheckboxes.length > 1) {
toDo.lastChild.remove();
}
// empties the only todo that was left on the DOM after clearance
if (allToDos[0].value !== "") {
allToDos[0].value = "";
}
});
}
clearBtn.addEventListener("click", () => {
clearToDosAndTitle();
});
const loadEmptyToDoInputs = () => {
const user = JSON.parse(localStorage.getItem(activeEmail.innerHTML));
if (user && user.length > 1) {
// using a while loop instead of a foreach in this case prevents an unrequested duplicate todo being added to the DOM
while (allToDos.length != user.length) {
createToDo();
}
}
}
const loadToDos = () => {
loadEmptyToDoInputs();
loadToDoTextValues();
loadCheckedToDoStyling();
loadDefaultEmptyToDo();
}
const loadCheckedToDoStyling = () => {
const user = JSON.parse(localStorage.getItem(activeEmail.innerHTML));
user.forEach(value => {
let checkedValues = [];
if (value.checked && toDoTextValues.includes(value.val)) {
checkedValues.push(value.val);
}
toDos = [...document.getElementsByClassName("input")].map(toDo => {
if (checkedValues.includes(toDo.value)) {
box = toDo.parentElement.firstChild;
box.checked = true;
toDo.style.textDecoration = "line-through";
toDo.style.opacity = "50%";
}
}
)});
}
const loadTitle = () => {
if (localStorage.getItem(activeEmail.innerHTML + " Title")) {
title.value = JSON.parse(localStorage.getItem(activeEmail.innerHTML + " Title"));
}
}
const loadDefaultEmptyToDo = () => {
// if there are no checkboxes, there are no todos, so load an empty one by default
if (allCheckboxes.length == 0) {
createToDo();
}
}
let toDoTextValues = [];
const loadToDoTextValues = () => {
const user = JSON.parse(localStorage.getItem(activeEmail.innerHTML));
if (user) {
user.forEach(value => {
toDoTextValues.push(value.val);
});
toDos = [...document.getElementsByClassName("input")].map(toDo => {
for (let value = 0; value < toDoTextValues.length - user.length + 1; value++) {
toDo.value = toDoTextValues[value];
}
toDoTextValues.length++;
});
}}
#title {
border: none;
font-size: 45px;
text-align: center;
margin-top: 20px;
}
#title::placeholder {
text-align: center;
}
#title:focus {
text-align: center;
outline: none;
}
#title:focus::placeholder {
visibility: hidden;
}
input[type="checkbox"] {
height: 20px;
width: 20px;;
}
.input {
border-top: none;
border-right: none;
border-left: none;
margin: 0 0 25px 30px;
font-size: 30px;
display: block;
}
.input:focus {
outline: none;
}
.X {
background-color: #E60E0E;
color: white;
border-radius: 50%;
height: 30px;
width: 30px;
font-size: 25px;
font-family: 'Helvetica', 'Arial', sans-serif;
display: flex;
justify-content: center;
align-items: center;
margin: -85px 100px 0 0;
float: right;
}
.X:hover {
cursor: pointer;
background-color: #d81313;
}
#toDoBtnContainer {
margin-top: 35px;
}
#add {
color: #fff;
font-weight: 400;
background-color: #27a348;
}
#add:hover {
background-color: #21af47;
}
#save {
color: #fff;
font-weight: 400;
background-color: #04992b;
}
#save:hover {
background-color: #099b30;
}
#clear {
color: #fff;
font-weight: 400;
background-color: #cc2121;
}
#clear:hover {
background-color: #d10f0f;
}
#saveMsg {
margin-top: 15px;
color: #04992b;
font-weight: 600;
}
.container {
display: flex;
justify-content: center;
height: 100vh;
padding-top: 120px;
}
.sub-container {
display: block;
}
.logo {
height: 75px;
width: 75px;
}
.headings {
text-align: center;
margin-bottom: 30px;
}
.headings h4 {
font-weight: 300;
}
button {
height: 35px;
font-size: 20px;
}
nav {
display: flex;
padding: 20px;
background-color: blue;
}
#signOut {
margin: 0 20px 0 auto;
color: #fff;
font-weight: 400;
background-color: #E22929;
}
#signOut:hover {
background-color: #db2626;
}
#activeEmail {
margin-right: 100px;
font-weight: 500;
}
<nav class="hide">
<button type="button" class="btn btn-lg pt-0 hide" id="signOut">Sign out</button>
<p class="mt-1 hide" id="activeEmail"></p>
</nav>
<div class="container">
<div class="sub-container">
<div class="d-flex justify-content-center mb-3" style="position: sticky;">
<img class="logo" src="/img/planning.svg" alt="tasktracker-logo">
</div>
<div class="headings">
<h1>TaskTracker</h1>
<h4>Leave no task untracked.</h4>
</div>
<div class="hide" id="toDoList">
<h2><input id="title" type="text" placeholder="Add Title"></h2>
<div id="toDoContainer">
</div>
<div id="toDoBtnContainer">
<button type="button" class="btn btn-lg pt-0" id="add">Add</button>
<button type="button" class="btn btn-lg pt-0" id="save">Save</button>
<button type="button" class="btn btn-lg pt-0" id="clear">Clear everything</button>
</div>
<p id="saveMsg"></p>
</div>
</div>
</div>
Your .container is a flex with fixed height. When todo-lists overflow, the flexbox tried to keep them all centered within the container.
An easy fix would be changing to min-height: 100vh. This way the flex can grow when the lists are added.
.container {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}

when adding a new item the functionality of the previous item changes

const addBtn = document.querySelector(".add");
const modal = document.querySelector(".modal__container");
const library = document.querySelector(".library__container");
const submitBook = document.querySelector(".add__book");
const deleteBtn = document.querySelector(".fas fa-trash-alt");
//Modal inputs
const modalTitle = document.querySelector("#title");
const modalAuthor = document.querySelector("#author");
const modalPages = document.querySelector("#pages");
const isRead = document.querySelector("#read-status");
//Toggle Modal
const hideModal = () => {
modal.style.display = "none";
};
const showModal = () => {
modal.style.display = "block";
const cancel = document.querySelector(".cancel");
cancel.addEventListener("click", (e) => {
e.preventDefault();
hideModal();
});
};
addBtn.addEventListener("click", showModal);
let myLibrary = [];
let index = 0;
function Book(title, author, pages, read) {
this.title = title,
this.author = author,
this.pages = pages,
this.read = read
}
submitBook.addEventListener("click", addBookToLibrary);
function addBookToLibrary(e) {
e.preventDefault();
let bookTitle = modalTitle.value;
let bookAuthor = modalAuthor.value;
let bookPages = modalPages.value;
let bookStatus = isRead.checked;
//Display error message if inputs are empty
if (bookTitle === "" || bookAuthor === "" || bookPages === "") {
const errorMessage = document.querySelector(".error__message--container");
hideModal();
errorMessage.style.display = "block";
const errorBtn = document.querySelector(".error-btn");
errorBtn.addEventListener("click", () => {
errorMessage.style.display = "none";
showModal();
})
} else {
let book = new Book(bookTitle, bookAuthor, bookPages, bookStatus);
myLibrary.push(book);
hideModal();
render();
}
}
function render() {
library.innerHTML = "";
for (let i = 0; i < myLibrary.length; i++) {
library.innerHTML +=
'<div class="book__container">' +
'<div class="book">' +
'<div class="title__content">' +
'<span class="main">Title : </span><span class="book__title">' +` ${myLibrary[i].title}`+'</span>' +
'</div>' +
'<div class="author__content">' +
'<span class="main">Author : </span><span class="book__author">'+` ${myLibrary[i].author}`+'</span>' +
'</div>' +
'<div class="pages__content">' +
'<span class="main">Pages : </span><span class="book__pages">'+` ${myLibrary[i].pages}`+'</span>' +
'</div>' +
'<div class="book__read-elements">' +
'<span class="book__read">I read it</span>' +
'<i class="fas fa-check"></i>' +
'<a href="#"><i class="fas fa-times"></i>' +
'<i class="fas fa-trash-alt"></i>' +
'</div>' +
'</div>' +
'</div>'
readStatus(myLibrary[i].checked)
}
modalTitle.value = "";
modalAuthor.value = "";
modalPages.value = "";
isRead.checked = false;
}
function readStatus(bookStatus) {
const bookStatusContainer = document.querySelector(".book__read");
if (bookStatus) {
bookStatusContainer.classList.add("yes");
bookStatusContainer.textContent = "I read it";
bookStatusContainer.style.color = "rgb(110, 176, 120)";
} else {
bookStatusContainer.classList.add("no");
bookStatusContainer.textContent = "I have not read it";
bookStatusContainer.style.color = "rgb(194, 89, 89)";
}
}
#import url('https://fonts.googleapis.com/css2?family=Poppins:wght#300;400;600&display=swap');
:root {
--light-gray: #dededef3;
--title-color: #333756;
--main-color: #c6c6c6f3;
}
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
font-family: 'Poppins', sans-serif;
background-color: var(--light-gray);
}
header {
text-align: center;
padding-top: 4rem;
color: var(--title-color);
text-transform: uppercase;
letter-spacing: 4px;
}
button {
margin: 1rem;
padding: 0.8rem 2rem;
font-size: 14px;
border-radius: 25px;
background: white;
color: #333756;
font-weight: 600;
border: none;
cursor: pointer;
transition: 0.6s all ease;
}
:focus {
/*outline: 1px solid white;*/
}
button:hover {
background: var(--title-color);
color: white;
}
.add__book:hover,
.cancel:hover {
background: var(--main-color);
color: var(--title-color)
}
.all,
.books__read,
.books__not-read {
border-radius: 0;
text-transform: uppercase;
letter-spacing: 0.1rem;
background: var(--light-gray);
border-bottom: 4px solid var(--title-color)
}
.library__container {
display: flex;
justify-content: center;
flex-wrap: wrap;
}
.book__container {
display: flex;
margin: 2rem 2rem;
}
.modal__container {
display: none;
position: fixed;
z-index: 4;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.4);
padding-top: 0px;
}
.book,
.modal {
padding: 2rem 2rem;
border-radius: 15px;
background: #333756;
line-height: 3rem;
}
.modal {
position: relative;
width: 50%;
margin: 0 auto;
margin-top: 8rem;
}
.modal__content {
display: flex;
flex-direction: column;
}
label {
color: white;
margin-right: 1rem;
}
input {
padding: 0.5rem;
font-size: 14px;
}
.book__read-elements {
display: flex;
justify-content: space-between;
}
.main,
i {
color: white;
pointer-events: none;
margin: 0.5rem;
}
.book__title,
.book__author,
.book__pages,
.book__read {
color: var(--main-color)
}
.error__message--container {
display: none;
position: fixed;
z-index: 4;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.4);
}
.error__message--modal {
position: relative;
margin: 0 auto;
margin-top: 10rem;
width:40%;
}
.error {
display: flex;
flex-direction: column;
align-items: center;
color: rgb(101, 3, 3);
font-size: 20px;
font-weight: bold;
background: rgb(189, 96, 96);
padding: 3rem 5rem;
border-radius: 10px;
}
.error-btn {
color: rgb(101, 3, 3);
font-weight: bold;
}
.error-btn:hover {
color: white;
background: rgb(101, 3, 3);
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.1/css/all.min.css" integrity="sha256-2XFplPlrFClt0bIdPgpz8H7ojnk10H69xRqd9+uTShA=" crossorigin="anonymous" />
<link rel="stylesheet" href="styles.css">
<title>Library</title>
</head>
<body>
<header>
<h1>My Library</h1>
<button class="add">Add New Book</button>
<div class="buttons">
<button class="all">View All</button>
<button class="books__read">Read</button>
<button class="books__not-read">Not Read</button>
</div>
</header>
<div class="error__message--container">
<div class="error__message--modal">
<div class="error">
<p>Complete the form!</p>
<button class ="error-btn">Ok</button>
</div>
</div>
</div>
<!--Modal-->
<form class="modal__container">
<div class="modal">
<div class="modal__content">
<label for="">Title:</label>
<input type="text" id="title">
</div>
<div class="modal__content">
<label for="">Author:</label>
<input type="text" id="author">
</div>
<div class="modal__content">
<label for="">Pages:</label>
<input type="number" id="pages">
</div>
<div>
<label for="read-status">Check the box if you've read this book</label>
<input type="checkbox" id="read-status" value ="check">
</div>
<button class="add__book">Add</button>
<button class="cancel">Cancel</button>
</div>
</form>
<!--End of Modal-->
<div class="library__container"></div>
<script src="script.js"></script>
</body>
</html>
I'm new to OOP and I'm struggling.
I'm building a library where you can add a book with the title, author nr of pages and if you've read it or not. When I add the first book if I check the box it displays that to book is not read(which is false). When I add a new book the read functionality is not applied to that book at all. I have no idea how to fix it
In this function you are checking the status if isRead which is incorrect.
Do this
Call the readStatus function inside the for loop
Pass the current parameter readStatus(myLibrary[i].checked)
Modify readStatus as shown below
function readStatus(status) {
const bookReadStatus = document.querySelector(".book__read");
if (status) {
bookReadStatus.classList.add("yes");
bookReadStatus.textContent = "I read it";
bookReadStatus.style.color = "rgb(110, 176, 120)";
} else {
bookReadStatus.classList.add("no");
bookReadStatus.textContent = "I have not read it";
bookReadStatus.style.color = "rgb(194, 89, 89)";
}
}

Categories