CSS hover no longer working when changing CSS properties through JS - javascript

I have this piece of code:
function dropdown() {
let dropdownText = document.querySelector(".dropdown-button");
let item = document.querySelector(".dropdown-items").getElementsByTagName("div")[0];
var aux = dropdownText.innerHTML;
dropdownText.innerHTML = item.innerHTML;
item.innerHTML = aux;
document.querySelector(".dropdown-items").style.display = "none";
}
.btn {
width: 150px;
height: 30px;
border-radius: 5px;
border: none;
box-shadow: 0 3px 1px 0 black;
text-align: center;
line-height: 30px;
color: black;
font-family: Consolas, monaco, monospace;
}
.dropdown {
margin: 0 50px 0 50px;
position: relative;
}
.dropdown-items {
display: none;
position: absolute;
}
.dropdown:hover .dropdown-button {
background: red;
}
.dropdown:hover .dropdown-items {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.dropdown-button {
background: orange;
font-size: 15px;
color: white;
}
.dropdown-button:hover {
cursor: pointer;
}
.dropdown-items div {
margin-top: 5px;
transform: scaleX(90%);
height: 20px;
line-height: 20px;
background: lightgray;
padding: 5px 0 5px 0;
text-align: center;
}
.dropdown-items div:hover {
cursor: pointer;
background: gray;
}
<div class="dropdown">
<button class="btn dropdown-button" type="button">Terminate</button>
<div class="dropdown-items">
<div class="btn" onclick="dropdown();">Interrupt</div>
</div>
</div>
As you can see, I am trying to make a dropdown. I also want to make it so that when I click an option in the dropdown, the dropdown items stop showing as the option has been selected. That's why I added the line document.querySelector(".dropdown-items").style.display = "none"; in the JS file as I thought the .dropdown:hover .dropdown-items part of my CSS would change back the display of those elements to visible when hovering again, but when hovering again after the first click, the dropdown does not show anymore. Why is happening and how can I fix this?

Inline styles override any stylesheet styles, as they have maximum CSS specificity.
Instead of working with inline styles (el.style.display = "none"), work with a CSS class open that you toggle. Also don't make use of inline event listeners like onclick. Those are insecure and widely considered bad practice for a whole bunch of reasons. Use addEventListener instead.
// get all the dropdowns in a NodeList
const dropdowns = document.querySelectorAll('.dropdown');
// iterate over the list
for (const dropdown of dropdowns) {
// for each dropdown, add a mouseenter and mouseleave listener
dropdown.addEventListener('mouseenter', function(event) {
dropdown.classList.add('open');
});
dropdown.addEventListener('mouseleave', function(event) {
dropdown.classList.remove('open');
});
// Now add a click listener to each <div class="dropdown-items">
// that transfers the text and closes the dropdown
dropdown.querySelector('.dropdown-items').addEventListener(
'click',
function(event) {
this.previousElementSibling.textContent = this.textContent;
dropdown.classList.remove('open');
}
);
}
.btn {
width: 150px;
height: 30px;
border-radius: 5px;
border: none;
box-shadow: 0 3px 1px 0 black;
text-align: center;
line-height: 30px;
color: black;
font-family: Consolas, monaco, monospace;
}
.dropdown {
display: inline-block;
position: relative;
}
.dropdown-items {
display: none;
position: absolute;
}
.dropdown:hover .dropdown-button {
background: red;
}
.dropdown.open .dropdown-items {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.dropdown-button {
background: orange;
font-size: 15px;
color: white;
}
.dropdown-button:hover {
cursor: pointer;
}
.dropdown-items div {
margin-top: 5px;
transform: scaleX(90%);
height: 20px;
line-height: 20px;
background: lightgray;
padding: 5px 0 5px 0;
text-align: center;
}
.dropdown-items div:hover {
cursor: pointer;
background: gray;
}
<div class="dropdown">
<button class="btn dropdown-button" type="button">Terminate</button>
<div class="dropdown-items">
<div class="btn">Interrupt</div>
</div>
</div>
<div class="dropdown">
<button class="btn dropdown-button" type="button">Terminate</button>
<div class="dropdown-items">
<div class="btn">Whatever</div>
</div>
</div>
<div class="dropdown">
<button class="btn dropdown-button" type="button">Terminate</button>
<div class="dropdown-items">
<div class="btn">Another one</div>
</div>
</div>
<div class="dropdown">
<button class="btn dropdown-button" type="button">Terminate</button>
<div class="dropdown-items">
<div class="btn">Here we go</div>
</div>
</div>

Related

I can't get the JavaScript toggle to work

What I want to do is when I click the task it will have a line through the text means that I'm done with the task. but the add event listener function for this is not working, I'm working with the javascript toggle and that's all I can think of right now to achieve this functionality.
Is there another way to do this? I searched on the internet and it seems complicated when I'm trying to figure it out.
const addBtn = document.querySelector("#push");
const taskInput = document.querySelector("#taskInput");
const taskOutput = document.querySelector("#tasks");
addBtn.addEventListener("click", function() {
let newTasks = taskInput.value;
if (newTasks.length == 0) {
alert("Please enter a task");
} else {
taskOutput.innerHTML += `<div class="task">
<span id="taskname">${newTasks} </span>
<button class="delete" id="deleteButton"><i class="fa-solid fa-trash"></i> </button>
</div>
`;
//delete
let deleteBtn = document.querySelector("#deleteButton");
deleteBtn.addEventListener("click", function() {
this.parentNode.remove();
});
//line through
let theTask = document.querySelectorAll(".task");
theTask.addEventListener("click", function() {
this.classList.toggle("completed");
});
}
});
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
height: 100vh;
background: linear-gradient( 90deg, rgba(241, 206, 221, 1) 0%, rgba(124, 184, 254, 1) 100%);
display: flex;
justify-content: center;
align-items: center;
font-family: 'Kumbh Sans', sans-serif;
}
.container {
border: 2px solid white;
width: 50%;
min-width: 450px;
margin: auto;
padding: 30px 40px;
}
#new-task {
position: relative;
background-color: white;
padding: 30px 20px;
border-radius: 1em;
}
#new-task input {
width: 70%;
height: 45px;
font-family: 'Manrope', sans-seif;
font-size: 1.2em;
border: 2px solid #d1d3d4;
padding: 12px;
color: #111111;
font-weight: 500;
position: relative;
border-radius: 5px;
}
#new-task input:focus {
outline: none;
border-color: violet;
}
#new-task button {
font-family: 'Manrope', sans-seif;
position: relative;
float: right;
width: 25%;
height: 45px;
border-radius: 5px;
font-weight: bold;
font-size: 16px;
border: none;
background-color: violet;
color: white;
cursor: pointer;
}
#tasks {
background-color: white;
padding: 30px 20px;
margin-top: 50px;
border-radius: 10px;
width: 100%;
}
.task {
background-color: white;
height: 50px;
padding: 5px 10px;
margin-top: 10px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 2px solid violet;
cursor: pointer;
}
.task span {
font-size: 18px;
font-weight: 400;
}
.task button {
background-color: violet;
color: white;
height: 100%;
width: 40px;
border: none;
border-radius: 5px;
outline: none;
cursor: pointer;
}
.task button:hover {
background-color: red;
}
.completed {
text-decoration: line-through;
}
<body>
<div class="container">
<div id="new-task">
<input type="text" name="" id="taskInput" placeholder="Task to be done" />
<button id="push">ADD</button>
</div>
<div id="tasks"></div>
</div>
<script src="/script.js"></script>
</body>
querySelectorAll will return the list of nodes matching the selector tasks. So you have to iterate through each of those nodes and add your listener. See the below code snippet
let theTasks = document.querySelectorAll(".task");
theTasks.forEach((task) => {
task.addEventListener("click", function() {
this.classList.toggle("completed");
});
});
theTask is a list of nodes. Trying to attach event listener on this list is causing issues.
Also, you will be inserting lots of buttons with same id deleteButton and spans with same id taskname which is incorrect and can cause undefined behavior.
For theTask fix, you may want to do something like:
let theTasks = [...document.querySelectorAll(".task")];
theTasks.forEach(task => {
task.addEventListener("click", function() {
this.classList.toggle("completed");
})
});
Using innerHTML to create manipulate the DOM for an application like a todo list is probably not a good idea. The answers to Advantages of createElement over innerHTML? give good explanations why.
It is worth noting that in the innerHTML code, the span and the button are created with an id and so all of these elements will have the same id. It is also probably not a good idea to have duplicate ids on one page. Why are duplicate ID values not allowed in HTML? explains why.
Also, adding event listeners to every new task is also probably not a good idea. What is DOM Event delegation? gives a good explanation why.
Finally, the Difference between HTMLCollection, NodeLists, and arrays of objects and Document.querySelectorAll() explain how to get lists of elements that can be manipulated.
So, I have rewritten the task creation code in the addBtn.addEventListener to show one way how this could be done with document.createElement().
And I have created a separate event listener on the Tasks container div, which handles both task deletion and task completion.
I also added the following CSS so that clicking on a trash can icon is handled by the parent button. Without this CSS, clicking on an icon would not delete the task.
div#tasks i {
pointer-events: none;
}
To make the todo list more visible in the code snippet below, I reduced the heights, margins, and paddings of some of the elements in the CSS.
I also added a link to the font awesome icon library.
const addBtn = document.querySelector("#push");
const taskInput = document.querySelector("#taskInput");
const taskOutput = document.querySelector("#tasks");
taskOutput.addEventListener("click", function(event) {
if (event.target && event.target.nodeName === "SPAN") {
event.target.classList.toggle("completed");
}
if (event.target && event.target.nodeName === "BUTTON") {
event.target.parentNode.remove();
}
});
addBtn.addEventListener("click", function() {
let newTasks = taskInput.value;
if (newTasks.length == 0) {
alert("Please enter a task");
} else {
// Create a task DIV
const newTaskElement = document.createElement("div");
newTaskElement.classList.add("task");
// Create a SPAN with the task name
const newTaskNameElement = document.createElement("span");
const taskTextnode = document.createTextNode(newTasks);
newTaskNameElement.appendChild(taskTextnode);
// Create a BUTTON with a TRASH CAN ICON
const newTaskDeleteButton = document.createElement("button");
const deleteImageElement = document.createElement("i");
deleteImageElement.classList.add("fa-solid", "fa-trash");
newTaskDeleteButton.appendChild(deleteImageElement);
// Append the SPAN and the BUTTON to the task DIV
newTaskElement.appendChild(newTaskNameElement);
newTaskElement.appendChild(newTaskDeleteButton);
// Append the task DIV to the TASK LIST DIV
taskOutput.appendChild(newTaskElement);
}
});
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
height: 100vh;
background: linear-gradient( 90deg, rgba(241, 206, 221, 1) 0%, rgba(124, 184, 254, 1) 100%);
font-family: 'Kumbh Sans', sans-serif;
}
/* ADDED TO MAKE SURE THAT THE TRASH ICON DOES NOT PROCESS CLICKS */
div#tasks i {
pointer-events: none;
}
.container {
border: 2px solid white;
width: 50%;
min-width: 450px;
margin: auto;
padding: 3px 4px;
}
#new-task {
position: relative;
background-color: white;
padding: 6px 4px;
border-radius: 1em;
}
#new-task input {
width: 70%;
height: 45px;
font-family: 'Manrope', sans-seif;
font-size: 1.2em;
border: 2px solid #d1d3d4;
padding: 12px;
color: #111111;
font-weight: 500;
position: relative;
border-radius: 5px;
}
#new-task input:focus {
outline: none;
border-color: violet;
}
#new-task button {
font-family: 'Manrope', sans-seif;
position: relative;
float: right;
width: 25%;
height: 45px;
border-radius: 5px;
font-weight: bold;
font-size: 16px;
border: none;
background-color: violet;
color: white;
cursor: pointer;
}
#tasks {
background-color: white;
padding: 6px 4px;
margin-top: 5px;
border-radius: 10px;
width: 100%;
min-height: 50px;
}
.task {
background-color: white;
height: 50px;
padding: 5px 10px;
margin-top: 10px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 2px solid violet;
cursor: pointer;
}
.task span {
font-size: 18px;
font-weight: 400;
}
.task button {
background-color: violet;
color: white;
height: 100%;
width: 40px;
border: none;
border-radius: 5px;
outline: none;
cursor: pointer;
}
.task button:hover {
background-color: red;
}
.completed {
text-decoration: line-through;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.3.0/css/all.min.css" rel="stylesheet" />
<div class="container">
<div id="new-task">
<input type="text" name="" id="taskInput" placeholder="Task to be done" />
<button id="push">ADD</button>
</div>
<div id="tasks"></div>
</div>

Why is a button firing the event of a previous element/tag?

I'm not sure what's causing the form button to fire the event of turning the page theme back to white after the dark mode has been clicked and enabled.
Not sure if that may be confusing, but for example if you open the page it's automatically on the light mode theme, when you click "dark" to switch the theme to dark and then click the button "search" while the theme is in "dark", the page will switch back to "light". What am I doing wrong or missing out? Please advise. Also, how could I refractor this JS better and simpler?
Thanks!
HTML - left out the head part intentionally
<html lang="en" color-mode="light">
<body>
<header class="header-container">
<h1 class="title">devfinder</h1>
<div class="light-dark mode">
<span
class="theme-toggle-btn light-hidden light"
aria-label="light theme toggle button">
LIGHT
<img class="light-icon" src="assets/icon-sun.svg" alt="" />
</span>
<span
class="theme-toggle-btn dark-hidden"
aria-label="dark theme toggle button">
DARK
<img src="assets/icon-moon.svg" alt="" />
</span>
</div>
</header>
<main class="content-container">
<section>
<form autocomplete="off" class="form" id="search">
<input
type="text"
id="search"
placeholder="Search GitHub username…" />
<button class="btn">Search</button>
</form>
JS
const themeBtn = document.querySelectorAll(".theme-toggle-btn");
const toggle = function (e) {
if (e.currentTarget.classList.contains("light-hidden")) {
document.documentElement.setAttribute("color-mode", "light");
localStorage.setItem("color-mode", "light");
return;
}
document.documentElement.setAttribute("color-mode", "dark");
localStorage.setItem("color-mode", "dark");
};
themeBtn.forEach((btn) => {
btn.addEventListener("click", toggle);
});
CSS
:root {
--monoFont: 'Space Mono', monospace;
--accent-blue: #0079FF;
--error-red: #F74646;
--light-hover: #60ABFF;
}
:root[color-mode="light"] {
--primary-text-color:#697C9A;
--secondary-text-color: #4B6A9B;
--accent-color: #2B3442;
--background-color: #F6F8FF;
--container-background: #FEFEFE;
--font-color: #222731;
}
:root[color-mode="dark"] {
--primary-text-color: #FFFFFF;
--background-color: #141D2F;
--container-background: #1E2A47;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
:root[color-mode="light"] .light-hidden,
:root[color-mode="dark"] .dark-hidden {
display: none;
}
body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: var(--background-color);
height: 100vh;
margin: 0;
color: var(--primary-text-color);
font-family: var(--monoFont);
}
.header-container {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 2em;
}
.header-container, .content-container {
width: 100%;
max-width: 730px;
}
/* header title */
.title {
color: var(--font-color);
font-size: 1.63rem;
}
/* theme toggle btn */
.theme-toggle-btn {
background-color: var(--background-color);
border: none;
color: var(--primary-text-color);
font-size: .7rem;
font-weight: 700;
letter-spacing: 1.5px;
cursor: pointer;
}
.theme-toggle-btn img {
margin: 0 0 -0.45em 0.75em;
width: 20px;
height: 20px;
}
/* search form */
.form {
position: relative;
display: flex;
align-items: center;
height: 69px;
}
.form input {
background-color: var(--container-background);
border: none;
width: 100%;
padding-left: 1.5em;
margin-bottom: 2em;
color: var(--font-color);
font-size: 1.05rem;
font-family: inherit;
font-weight: 400;
border-radius: 10px;
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;;
}
.form input::placeholder {
color: var(--secondary-text-color);
}
.btn {
position: absolute;
top: 50%;
right: 10px;
transform: translateY(-80%);
width: 100%;
max-width: 106px;
height: 50px;
background-color: var(--accent-blue);
border: none;
color: #FFFFFF;
font-size: 1rem;
border-radius: 10px;
cursor: pointer;
}
.btn:hover {
background-color: var(--light-hover);
}
I think when you're clicking the "Search" button, it's refreshing the page and reloads the html template. Because you have "color-mode="light" within the html element, it will then load the page in light mode rather than dark mode.
I think the problem is: your not consuming the selection from localStorage. To persist the user selection, you should check the preference in localStorage when the page loads.

How can I focus the onclick onto this icon? (HTML, CSS, JS)

I need help with figuring out how to focus the 'clicking' part of this dropdown navbar icon so that I don't have to click a little to the left of the icon (also the other navbar items) since I'm trying to recreate Mac OS's navbar.
function myFunction() {
document.getElementById("myDropdown").classList.toggle("show");
}
// Close the dropdown if the user clicks outside of it
window.onclick = function(e) {
if (!e.target.matches('.dropbtn')) {
var myDropdown = document.getElementById("myDropdown");
if (myDropdown.classList.contains('show')) {
myDropdown.classList.remove('show');
}
}
}
.navbar {
overflow: hidden;
background-color: #333;
font-family: Arial, Helvetica, sans-serif;
}
.navbar a {
float: left;
font-size: 12px;
color: white;
text-align: center;
padding: 10px 10px;
text-decoration: none;
}
.dropdown {
float: left;
overflow: hidden;
}
.dropdown .dropbtn {
cursor: pointer;
font-size: 12px;
border: none;
outline: none;
color: white;
padding: 5px 10px;
background-color: inherit;
font-family: inherit;
margin: 0;
}
<div class="dropdown">
<button class="dropbtn" onclick="myFunction()">
<i class="fa-solid fa-power-off"></i>
</button>
<div class="dropdown-content" id="myDropdown">
Link 1
Link 2
Link 3
</div>
</div>
IMG:
Problem
I found the source where OP came from and I am not surprised why you ran into trouble. W3Schools is a good resource because it's simple and never over explains things but at times it omits or just overlooks certain details. In the W3School example "Dropdown Menu Inside a Navigation Bar" the following segment of a CSS ruleset is wrong:
.dropbtn:focus {
background-color: red;
}
focus event only applies to the these tags:
<input>
<textarea>
<select>
<a>
It may vary between browsers but the above list is standard. So <button> is usually not focusable.
Solution
Change the <button> into an <a>
Add e.preventDefault(); to the event handler so the page won't jump when the <a> is clicked.
The rest of the changes are recommended, but not necessary. Although I strongly suggest that you don't use inline event handlers:
<button onclick="lame(this)">Don't do this</button>
Instead use:
// onevent property
document.querySelector('button').onclick = better;
OR
// event listener
document.querySelector('button').addEventListener('click', best);
const menu = document.querySelector("menu");
document.querySelector('.btn').onclick = toggleMenu;
function toggleMenu(e) {
e.preventDefault();
menu.classList.toggle("show");
}
window.onclick = function(e) {
if (!e.target.matches('.btn, .btn *') && menu.classList.contains('show')) {
menu.classList.remove('show');
}
}
html {
font: 300 2vmax/1.2 'Segoe UI';
}
nav {
display: flex;
overflow: hidden;
background-color: #333;
}
nav a {
display: block;
padding: 0.75rem 1.2rem;
color: white;
text-align: center;
text-decoration: none;
}
.dropdown {
min-width: 7.75rem;
overflow: hidden;
}
.btn {
display: flex;
justify-content: space-between;
align-items: center;
margin: 0;
padding: 0.75rem 1.2rem;
border: none;
outline: none;
color: white;
text-align: left;
}
.btn * {
display: block;
font-weight: 300;
}
.btn i {
padding-top: 0.15rem;
}
nav a:hover,
.btn:focus {
background-color: red;
}
menu {
position: absolute;
display: none;
min-width: 7.75rem;
margin: 0;
padding-left: 0;
background-color: #f9f9f9;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
}
menu a {
padding: 0.5rem 1.2rem;
text-align: left;
color: black;
}
menu a:hover {
background-color: #ddd;
}
.show {
display: block;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<nav>
Home
News
<div class='dropdown'>
<a href='#' class="btn"><b>Menu</b>
<i class="fa fa-chevron-circle-down"></i>
</a>
<menu>
Link 1
Link 2
Link 3
</menu>
</div>
</nav>

Delete button not working after adding another entry to my Library object

I have a problem that my delete button (the yellow one) works perfectly on preloaded book library items, but it isn't on the new entry when I add a book and want to delete it... I also want you to ask what it would be the easiest way to delete books also from myLibrary array... Thanks.
I've attached my code here. Any help will be appreciated. Thanks in advance.
//DOM
const bookForm = document.querySelector(".book-form");
// Calling a form when clicking on add book button
function openNav() {
document.getElementById("myNav").style.height = "100%";
console.log("dsafsa");
}
function closeNav() {
document.getElementById("myNav").style.height = "0%";
}
// where the books will be saved...
let myLibrary = [{
title: "Harry Potter - and the Philosopher's Stone",
author: "J. K. Rowling",
pages: 223,
readStatus: "no",
},
{
title: "The Hobbit",
author: "J.R.R. Tolkien",
pages: 304,
readStatus: "yes",
},
];
// book object
function Book(title, author, pages, readStatus) {
(this.title = title),
(this.author = author),
(this.pages = pages),
(this.readStatus = readStatus);
}
let i = "";
// render the book on page load...
function render() {
const books = myLibrary;
books.forEach((book) => {
addNewBookUI(book);
});
}
render();
document.querySelector(".book-form").addEventListener("submit", (e) => {
// prevent actual submit
e.preventDefault();
// get values
const title = document.querySelector("#title").value;
const author = document.querySelector("#author").value;
const pages = document.querySelector("#pages").value;
const readStatus = document.querySelector('input[name="yes_no"]:checked')
.value;
// prevent empty fields ...
if (title === "" || author === "" || pages === "0") {
alert("Missing data");
} else {
const book = new Book(title, author, pages, readStatus);
myLibrary.push(book);
addNewBookUI(book);
clearFormFields()
}
});
function addNewBookUI(book) {
if (book.readStatus === "yes") {
i = "checked";
} else {
i = "";
}
const main = document.querySelector(".main");
const bookCard = document.createElement("div");
bookCard.classList.add("book-card");
bookCard.innerHTML = `<div class="delete_button"><button class="delete btn"><i class="fa fa-trash">
</i></button></div><div class="title">${book.title}</div><div class="author">${book.author}
</div><div class="pages">${book.pages}</div><div class="read_status">Read: <input type="checkbox" id="yes" name="readstatus" value="yes" ${i}>
</div>`;
main.appendChild(bookCard);
}
// clear form fields after submit
function clearFormFields() {
const myForm = document.getElementById("myForm");
myForm.reset();
}
// Add event listener to all deleteButton
const deleteButton = document.querySelectorAll(".delete");
// deletes book UI;
deleteButton.forEach((el) => {
el.addEventListener("click", function () {
el.parentElement.parentElement.remove()
console.log("sas")
})
})
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
font-family: 'Roboto', sans-serif;
margin: 0;
padding: 0;
}
header {
color: #ffffff;
display: flex;
font-size: 1.4rem;
justify-content: space-between;
align-items: center;
background-color: #155999;
border-bottom: #172f4f solid 10px;
}
.logo {
margin-left: 10px;
}
header button {
background-color: #183153;
border: none;
text-align: left;
font-size: 0.9rem;
border-radius: 5px;
color: #ffffff;
padding: 10px 50px;
margin-right: 10px;
cursor: pointer;
}
.plus-sign {
padding-right: 7px;
}
.main {
position: absolute;
width: 100%;
height: 100%;
background-color: #183153;
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
}
.book-card {
text-align: center;
font-weight: 1000;
display: flex;
flex-direction: column;
justify-content: space-evenly;
width: 250px;
height: 350px;
border-radius: 10px;
background-color: #155999;
margin-left: 20px;
margin-top: 10px;
color: #ffffff;
border: #172f4f solid 8px;
line-height: 30px;
position: relative;
padding-left: 7px;
padding-right: 7px;
box-shadow: 10px 4px 22px -5px rgba(21, 89, 153, 1);
}
.overlay {
height: 0%;
width: 100%;
position: fixed;
z-index: 1;
top: 0;
left: 0;
background-color: rgb(21, 89, 153);
background-color: rgba(21, 89, 153, 0.7);
overflow-y: hidden;
transition: 0.5s;
}
.overlay-content {
position: relative;
top: 25%;
width: 100%;
text-align: center;
margin-top: 30px;
display: flex;
flex-direction: column;
font-size: 30px;
}
.overlay a {
padding: 8px;
text-decoration: none;
font-size: 36px;
color: #ffffff;
display: block;
transition: 0.3s;
}
.overlay a:hover,
.overlay a:focus {
color: #c3c6d1;
}
.overlay .closebtn {
position: absolute;
top: 20px;
right: 45px;
font-size: 60px;
}
#media screen and (max-height: 450px) {
.overlay {
overflow-y: auto;
}
.overlay a {
font-size: 20px
}
.overlay .closebtn {
font-size: 40px;
top: 15px;
right: 35px;
}
}
.book-card div {
margin-top: 15px;
}
/* Style buttons */
.btn {
background-color: #ffd43b;
/* Blue background */
border: none;
/* Remove borders */
color: red;
/* White text */
padding: 12px 16px;
/* Some padding */
font-size: 16px;
/* Set a font size */
cursor: pointer;
/* Mouse pointer on hover */
border-radius: 50%;
position: absolute;
top: -20px;
right: -15px;
}
/* Darker background on mouse-over */
.btn:hover {
background-color: #183153;
}
.delete_button {
top: 0;
right: 0;
}
form div {
margin-top: 15px;
font-family: 'Roboto', sans-serif;
color: white;
font-weight: bold;
font-size: 30px;
margin-bottom: 10px;
}
.radiobutton {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.form-flex {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
.form-flex input {
width: 50%;
border-radius: 5px;
height: 30px;
margin-top: 5px
}
.form-flex input::placeholder {
text-align: center;
}
input[type=submit] {
background-color: #183153;
border: none;
text-align: left;
font-size: 0.9rem;
border-radius: 5px;
color: #ffffff;
padding: 10px 50px;
margin-top: -30px;
cursor: pointer;
}
.radiobutton p {
margin-top: 10px;
margin-bottom: 30px;
}
.flexbuttons {
display: flex;
margin-top: -50px;
margin-bottom: -25px;
}
input[type="text"] {
font-size: 24px;
text-align: center;
}
<!-- The overlay -->
<div id="myNav" class="overlay">
<!-- Button to close the overlay navigation -->
×
<!-- Overlay content -->
<div class="overlay-content">
<form id="myForm" class="book-form">
<div class="form-flex">
<label for="title">Book name:</label>
<input type="text" id="title" name="title" placeholder="Book name...">
</div>
<div class="form-flex">
<label for="author">Book author:</label>
<input type="text" id="author" name="author" placeholder="Book author...">
</div >
<div class="form-flex">
<label for="pages">Pages:</label>
<input type="number" id="pages" placeholder="0" name="pages">
</div>
<div class="radiobutton">
<p>Have you read a book?</p>
<div class="flexbuttons">
<div>
<p><input type="radio" id="huey" name="yes_no" value="yes" checked>
<label for="huey">yes</label>
</p>
</div>
<div>
<p><input type="radio" id="no" name="yes_no" value="no">
<label for="dewey">no</label>
</p>
</div>
</div>
</div>
<div><input type="submit" value="Add Book"></div>
</form>
</div>
</form>
</div>
</div>
</div>
<header>
<div class="logo">
<h1><i class="fa fa-book" aria-hidden="true"></i>
</i>Library
</h1>
</div>
<div class="button">
<button onclick="openNav()" class="add-book">
<i class="fa fa-plus plus-sign"></i>
Add book</button>
</div>
</header>
<div class="main">
</div>
<script src="./index.js" defer></script>
<script src="https://use.fontawesome.com/30a34909cc.js"></script>
You assigned the click event at loading time to all existing buttons with class=="delete". This will naturally not include the ones you might add dynamically at a later stage.
If you want all ".delete" buttons to have the click-event attached to them you need to do a "delegated event attachment" (edited, removes myLibrary element too now):
// Add event listener to all current and future deleteButtons
document.querySelector('.main').onclick=ev=>{
let el= ev.target.classList.contains('fa-trash')? ev.target.parentElement : ev.target.classList.contains('delete') ? ev.target : false;
if (el) {
let card=el.parentElement.parentElement; // book-card DOM element
// remove myLibrary array-element here:
myLibrary.splice( [...card.parentElement.children].indexOf(card) ,1);
console.log(myLibrary)
// remove card DOM element:
card.remove()
}
}
This will bind the click event handler to the .main div and will react only if the clicked element has a class=='fa-trash' or class=='delete'. In the first case it will "move up" one level (assign the parent element, i. e. the button to el), otherwise the clicked element is the button itself. If none of these classes are found, el becomes false and nothing happens. Otherwise the "Grandparent" of el is removed with `el.parentElement.parentElement.remove()' .
And please try and make your MCVE a little smaller next time, as it no fun to handle this amount of code in a small Stackoverflow snippet window! A true MCVE will get you more and faster responses!
Below is a working snippet, check it out:
//DOM
const bookForm = document.querySelector(".book-form");
// Calling a form when clicking on add book button
function openNav() {
document.getElementById("myNav").style.height = "100%";
console.log("dsafsa");
}
function closeNav() {
document.getElementById("myNav").style.height = "0%";
}
// where the books will be saved...
let myLibrary = [
{
title: "Harry Potter - and the Philosopher's Stone",
author: "J. K. Rowling",
pages: 223,
readStatus: "no",
},
{
title: "The Hobbit",
author: "J.R.R. Tolkien",
pages: 304,
readStatus: "yes",
},
];
// book object
function Book(title, author, pages, readStatus) {
(this.title = title),
(this.author = author),
(this.pages = pages),
(this.readStatus = readStatus);
}
let i = "";
// render the book on page load...
function render() {
const books = myLibrary;
books.forEach((book) => {
addNewBookUI(book);
});
}
render();
document.querySelector(".book-form").addEventListener("submit", (e) => {
// prevent actual submit
e.preventDefault();
// get values
const title = document.querySelector("#title").value;
const author = document.querySelector("#author").value;
const pages = document.querySelector("#pages").value;
const readStatus = document.querySelector('input[name="yes_no"]:checked')
.value;
// prevent empty fields ...
if (title === "" || author === "" || pages === "0") {
alert("Missing data");
} else {
const book = new Book(title, author, pages, readStatus);
myLibrary.push(book);
addNewBookUI(book);
clearFormFields()
}
});
function addNewBookUI(book) {
if (book.readStatus === "yes") {
i = "checked";
} else {
i = "";
}
const main = document.querySelector(".main");
const bookCard = document.createElement("div");
bookCard.classList.add("book-card");
bookCard.innerHTML = `<div class="delete_button"><button class="delete btn"><i class="fa fa-trash">
</i></button></div><div class="title">${book.title}</div><div class="author">${book.author}
</div><div class="pages">${book.pages}</div><div class="read_status">Read: <input type="checkbox" id="yes" name="readstatus" value="yes" ${i}>
</div>`;
main.appendChild(bookCard);
}
// clear form fields after submit
function clearFormFields() {
const myForm = document.getElementById("myForm");
myForm.reset();
}
// Add event listener to all deleteButton
document.querySelector('.main').onclick=ev=>{
let el= ev.target.classList.contains('fa-trash')? ev.target.parentElement : ev.target.classList.contains('delete') ? ev.target : false;
if (el) {
let card=el.parentElement.parentElement;
myLibrary.splice( [...card.parentElement.children].indexOf(card) ,1);
console.log(myLibrary)
card.remove()
}
}
*, *::before, *::after {
box-sizing: border-box;
}
body {
font-family: 'Roboto', sans-serif;
margin: 0;
padding: 0;
}
header {
color: #ffffff;
display: flex;
font-size: 1.4rem;
justify-content: space-between;
align-items: center;
background-color:#155999;
border-bottom: #172f4f solid 10px;
}
.logo {
margin-left: 10px;
}
header button {
background-color: #183153;
border: none;
text-align: left;
font-size: 0.9rem;
border-radius: 5px;
color: #ffffff;
padding: 10px 50px;
margin-right: 10px;
cursor: pointer;
}
.plus-sign {
padding-right: 7px;
}
.main {
position: absolute;
width: 100%;
height: 100%;
background-color: #183153;
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
}
.book-card {
text-align: center;
font-weight: 1000;
display: flex;
flex-direction: column;
justify-content: space-evenly;
width: 250px;
height: 350px;
border-radius: 10px;
background-color: #155999;
margin-left: 20px;
margin-top:10px;
color: #ffffff;
border: #172f4f solid 8px;
line-height: 30px;
position: relative;
padding-left: 7px;
padding-right: 7px;
box-shadow: 10px 4px 22px -5px rgba(21,89,153,1);
}
.overlay {
height: 0%;
width: 100%;
position: fixed;
z-index: 1;
top: 0;
left: 0;
background-color: rgb(21,89,153);
background-color: rgba(21,89,153,0.7);
overflow-y: hidden;
transition: 0.5s;
}
.overlay-content {
position: relative;
top: 25%;
width: 100%;
text-align: center;
margin-top: 30px;
display: flex;
flex-direction: column;
font-size: 30px;
}
.overlay a {
padding: 8px;
text-decoration: none;
font-size: 36px;
color: #ffffff;
display: block;
transition: 0.3s;
}
.overlay a:hover, .overlay a:focus {
color: #c3c6d1;
}
.overlay .closebtn {
position: absolute;
top: 20px;
right: 45px;
font-size: 60px;
}
#media screen and (max-height: 450px) {
.overlay {overflow-y: auto;}
.overlay a {font-size: 20px}
.overlay .closebtn {
font-size: 40px;
top: 15px;
right: 35px;
}
}
.book-card div {
margin-top:15px;
}
/* Style buttons */
.btn {
background-color: #ffd43b; /* Blue background */
border: none; /* Remove borders */
color: red; /* White text */
padding: 12px 16px; /* Some padding */
font-size: 16px; /* Set a font size */
cursor: pointer; /* Mouse pointer on hover */
border-radius: 50%;
position:absolute;
top:-20px;
right:-15px;
}
/* Darker background on mouse-over */
.btn:hover {
background-color: #183153;
}
.delete_button {
top:0;
right:0;
}
form div {
margin-top: 15px;
font-family: 'Roboto', sans-serif;
color:white;
font-weight: bold;
font-size: 30px;
margin-bottom: 10px;
}
.radiobutton {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.form-flex {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
.form-flex input {
width: 50%;
border-radius: 5px;
height: 30px;
margin-top: 5px
}
.form-flex input::placeholder {
text-align: center;
}
input[type=submit] {
background-color: #183153;
border: none;
text-align: left;
font-size: 0.9rem;
border-radius: 5px;
color: #ffffff;
padding: 10px 50px;
margin-top: -30px;
cursor: pointer;
}
.radiobutton p {
margin-top:10px;
margin-bottom: 30px;
}
.flexbuttons {
display: flex;
margin-top: -50px;
margin-bottom: -25px;
}
input[type="text"]
{
font-size:24px;
text-align: center;
}
JS:
//DOM
const bookForm = document.querySelector(".book-form");
<!-- The overlay -->
<div id="myNav" class="overlay">
<!-- Button to close the overlay navigation -->
×
<!-- Overlay content -->
<div class="overlay-content">
<form id="myForm" class="book-form">
<div class="form-flex">
<label for="title">Book name:</label>
<input type="text" id="title" name="title" placeholder="Book name...">
</div>
<div class="form-flex">
<label for="author">Book author:</label>
<input type="text" id="author" name="author" placeholder="Book author...">
</div >
<div class="form-flex">
<label for="pages">Pages:</label>
<input type="number" id="pages" placeholder="0" name="pages">
</div>
<div class="radiobutton">
<p>Have you read a book?</p>
<div class="flexbuttons">
<div><p><input type="radio" id="huey" name="yes_no" value="yes" checked>
<label for="huey">yes</label></p>
</div>
<div><p><input type="radio" id="no" name="yes_no" value="no">
<label for="dewey">no</label></p>
</div>
</div>
</div>
<div><input type="submit" value="Add Book"></div>
</form>
</div>
</form>
</div>
</div>
</div>
<header>
<div class="logo">
<h1><i class="fa fa-book" aria-hidden="true"></i>
</i>Library</h1>
</div>
<div class="button">
<button onclick="openNav()" class="add-book">
<i class="fa fa-plus plus-sign"></i>
Add book</button>
</div>
</header>
<div class="main">
</div>
<script src="./index.js" defer></script>
<script src="https://use.fontawesome.com/30a34909cc.js"></script>

`transitionend` stops firing after several events

I have a tricky problem. When a user presses on a div I want to add a css class to that div with name .active, using css transition. Then, after a short timeout, I want to handle transitionend event where I will remove the class .active from the div. The problem is, if a user presses on the button too fast, say 15-20 times per second, transitionend eventually stops firing.
You can see this effect here(clickable link), open Chrome browser(I wasn't able to reproduce it in FF) and start clicking on the button as fast as you can. After 15-20 clicks transitionend will stop triggering.
I think, when transitionend handler is still working, a user can press the button once again and the div will get .active class, but it will not trigger transition event. The question is - is it possible to write bullet-proof code to clear the .active class using transitionend event only ?
Thank you.
*edit* embedded snippet below
var blk = document.querySelector('.animated-element');
var btn = document.querySelector('.button');
var ctr = document.querySelector('.click-counter');
var lgr = document.querySelector('.logger');
btn.addEventListener('click', function() {
var currentValue = Number(ctr.innerHTML);
ctr.innerHTML = ++currentValue;
if (!blk.classList.contains('active')) {
blk.classList.add('active');
}
});
blk.addEventListener('transitionend', function() {
blk.classList.remove('active');
var li = document.createElement('li');
li.innerHTML = 'Transition end';
lgr.appendChild(li);
});
.scene {
position: relative;
width: 300px;
height: 300px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.animated-element {
width: 200px;
height: 200px;
margin-bottom: 10px;
display: flex;
justify-content: center;
align-items: center;
font-size: 2em;
background-color: #ff0000;
color: white;
border-radius: 5px;
transition: all 500ms;
}
.animated-element.active {
font-weight: 700;
/* background-color: #ff0055; */
}
.button {
padding: 5px 10px;
border: 2px solid black;
border-radius: 5px;
text-align: center;
cursor: pointer;
user-select: none;
}
.click-counter {
position: absolute;
top: 0;
left: 0;
min-height: 17px;
min-width: 17px;
padding: 2px 4px;
color: gray;
font-weight: bold;
text-align: center;
border: 2px solid gray;
border-radius: 50%;
user-select: none;
}
.logger {
width: 300px;
padding: 0;
list-style: none;
text-align: center;
}
<body>
<main>
<div class="scene">
<div class="click-counter">0</div>
<div class="animated-element">
<span>ABC</span>
</div>
<div class="button">
Press me several times
</div>
</div>
<ul class="logger"></ul>
</main>
</body>

Categories

Resources