I am having problems trying to toggle an icon in javascript - javascript

I am trying to toggle my edit button to the class of hidden and my trash button to the class of show. but I am not able to target the elements I always am getting the error of NULL would anyone be able to help I am stuck.
I have tried many ways but I cant get the edit button to toggle to display none and my trash button to toggle to display flex thanks guys for any help
HTML
```
TO-DO LIST
Today's To Do
<div class="todo-list"></div>
<button id="clear-btn" class="clear-btn" type="button"
name="button">
Clear all completed
</button>
</div>
</body>
</html>
```
const clear = document.querySelector(".clear-btn");
const list = document.querySelector(".todo-list");
const input = document.getElementById("add-input");
const form = document.getElementById("todoform");
let todos = [];
const LINE_THROUGH = "lineThrough";
form.addEventListener("submit", (e) => {
e.preventDefault();
saveTodo();
});
function saveTodo() {
const todoValue = input.value;
todos.push({
value: todoValue,
completed: false,
});
input.value = "";
renderTodos();
}
function renderTodos() {
list.innerHTML = "";
todos.forEach((todo, index) => {
list.innerHTML += `
<div class="todo" id=${index}>
<i class="fa ${
todo.checked ? "solid fa-check" : "regular fa-square"
}" data-action="check"
></i>
<p class=${todo.checked ? LINE_THROUGH : ""} data-
action="check">${todo.value}</p>
<span class= "edit">
<i class='fas fa-ellipsis-v edit-task ' data-action="edit">
</i>
</span>
<i class="fa-solid fa-trash-can trash-btnn hidden " data-
action="delete">
</i>
</div>
`;
});
}
list.addEventListener("click", (event) => {
const { target } = event;
const parentElement = target.parentNode;
if (parentElement.className !== "todo") return;
const todo = parentElement;
const todoId = Number(todo.id);
const { action } = target.dataset;
action === "check" && checkTodo(todoId);
// action === 'edit' && checkTodo(todoId);
// action === 'delete' && checkTodo(todoId);
});
function checkTodo(todoId) {
todos = todos.map((todo, index) => ({
...todo,
checked: index === todoId ? !todo.checked : todo.checked,
}));
renderTodos();
}
const threeVBtn = document.querySelector(".edit-task");
const trashBtn = document.querySelector(".trash-btnn");
threeVBtn.addEventListener("click", (e) => {
trashBtn.classList.toggle("show");
threeVBtn.classList.toggle("hidden");
});
.hidden {
display: none;
}
.trash-btnn {
cursor: pointer;
color: rgb(204, 16, 47);
}
.show {
display: flex;
}

It is because on your first render there is no todo initially and the element specified by querySelector for threeVBtn returns undefined. One simple way is to check if the element is added to document before attaching event listener, like this:
if (threeVBtn) {
threeVBtn.addEventListener("click", (e) => {
trashBtn.classList.toggle("show");
threeVBtn.classList.toggle("hidden");
});
//perform any action for once threeVBtn is added to Document
}

The problem is when you run any of the DOM selection methods (querySelector, getElementByClassName, etc), it only grabs what's currently in the DOM (ie. currently rendered html). When you add or remove items from the DOM, you must re-select elements to get the current items. It's kind of a pain, but there is a solution.
There is a concept called "event delegation" which is a technique that solves this problem. Delegation works by picking a parent element of the dynamic list - it can be any parent element so long as the element stays in the DOM while your list is dynamically changing. Many people just use the <body> since it's always guaranteed to be there. Whenever something inside the parent element is clicked, the event does this cool thing called "bubbling" - which is beyond the scope of this answer. You can read all about DOM events, bubbling, and delegation on MDN.
You can attach your click handlers to the <body> element and check the event.target for the different types of things you want, best shown by example:
document.body.addEventListener('click', (ev) => {
const el = event.target;
if (el.classList.contains("edit-task")) {
// edit button was clicked
ev.preventDefault();
return;
}
if (el.classList.contains("trash-btnn")) {
// trash button was clicked
ev.preventDefault();
return;
}
});

Related

My span won't delete or not getting targetted

I don't understand why my span isn't getting deleted when all others is behaving like they supposed. Here's my function.
const item = e.target;
if (item.classList[0] === 'trash-btn') {
// this works
const task = item.parentElement.parentElement;
task.classList.add('vanish');
task.remove();
}
if (item.classList[0] === 'text-duedate') {
// this is not working
item.remove();
}
if (item.classList[0] === 'check-btn') {
// this works
const task = item.parentElement.parentElement;
completed.appendChild(task);
item.remove();
completed.addEventListener('click', statusCheck);
}
The div that I am targeting:
<div class="card-date">
<span class="text-duedate">Due: </span>
<span class="alert">9/12/22</span>
</div>
Any help is appreciated.
Maybe the text-duedate class is not the first class in the classList, I suggest to use contains
if (item.classList.contains('text-duedate')) {
// this is not working
item.remove();
}
NOTE
The target element is the element who clicked not who fired the event, so maybe a child element of it, I suggest you use currentTarget.
const item = e.currentTarget;
I just ran your code and it seems to work fine
<div class="card-date">
<span class="text-duedate">Due:</span>
<span class="alert">9/12/22</span>
</div>
and JS
window.addEventListener('click', (e) => {
const item = e.target;
if (item.classList.contains('text-duedate')) {
item.remove();
}
})
thank you for the help. This was the only way I could get it to work.
if (item.classList[0] === 'trash-btn') {
const task = item.parentElement.parentElement;
task.classList.add('vanish');
// task.addEventListener('transition-end', function(){
// task.remove();
// });
task.remove();
}
let elems = document.querySelector('.text-duedate');
elems.remove();
if (item.classList.contains('check-btn')) {
const task = item.parentElement.parentElement;
completed.appendChild(task);
item.remove();
completed.addEventListener('click', statusCheck);
}

ClassList toggle works only for some elements

I am an absolute beginner in this field. And what i m doing is a small todo-list page.
And I am stuck in the following 'button events' part.
As in the code, I want to toggle the class of 'completed' to the ancestor div of the button clicked. However, it seems to work only for some elements.
Meaning, if the nodelist of 'finishBtn' variable has an odd number length, the class list only toggles for even number indexes and vice versa.
For example, if nodelist.length = 3, then the class list only toggles for nodelist[0]
and nodelist[2].
(However, for removeBtn variable, it works just fine)
Thank you very much for your time. I would really appreciate every reply of yours.
Cos I m stuck in this bloody thing for hours.
addBtn.addEventListener('click', (e)=> {
e.preventDefault();
if(input.value === ''){
return;
}
// adding elements to the body;
const eachTodo = document.createElement('div');
eachTodo.classList.add('eachTodo');
const textName = document.createElement('p');
textName.textContent = input.value;
const btns = document.createElement('div');
btns.classList.add('btns');
const finish = document.createElement('button');
finish.classList.add('finish');
finish.textContent = 'Finish';
const remove = document.createElement('button');
remove.classList.add('remove');
remove.textContent = 'Remove';
btns.append(finish, remove);
eachTodo.append(textName, btns);
plansDiv.append(eachTodo);
input.value = '';
//button Events
const finishBtn = document.querySelectorAll('.finish');
const removeBtn = document.querySelectorAll('.remove');
finishBtn.forEach(btn => {
btn.addEventListener('click', ()=>{
btn.parentElement.parentElement.classList.toggle('completed');
})
})
removeBtn.forEach(btn => {
btn.addEventListener('click', ()=>{
btn.parentElement.parentElement.remove();
})
})
})
This is my CSS part
.completed p {
text-decoration: line-through;
opacity: 0.8;
}
As it stands you're querying all buttons on each add and adding new listeners to them all resulting in duplicate listeners on each button that fire sequentially.
Elements with an even number of listeners attached will toggle the class an even number of times and return to the initial value.
"on" toggle-> "off" toggle-> "on"
Elements with an odd number of attached listeners toggle the class an odd number of times and appear correct at the end.
"on" toggle-> "off" toggle-> "on" toggle-> "off"
(It works for Remove because the first listener removes the element and subsequent Removes don't change that.)
Add listeners only once to each button
You can avoid this by simply adding the listeners directly to the newly created buttons. You already have references to each button and their parent element (eachTodo) in the script, so you can just add the listeners directly to them and reference the parent directly.
finish.addEventListener('click', () => {
eachTodo.classList.toggle('completed');
});
remove.addEventListener('click', () => {
eachTodo.remove();
});
const addBtn = document.getElementById('addBtn');
const plansDiv = document.getElementById('plansDiv');
const input = document.getElementById('input');
addBtn.addEventListener('click', (e) => {
e.preventDefault();
if (input.value === '') {
return;
}
// adding elements to the body;
const eachTodo = document.createElement('div');
eachTodo.classList.add('eachTodo');
const textName = document.createElement('p');
textName.textContent = input.value;
const btns = document.createElement('div');
btns.classList.add('btns');
const finish = document.createElement('button');
finish.classList.add('finish');
finish.textContent = 'Finish';
const remove = document.createElement('button');
remove.classList.add('remove');
remove.textContent = 'Remove';
btns.append(finish, remove);
eachTodo.append(textName, btns);
plansDiv.append(eachTodo);
input.value = '';
// Add listeners directly
finish.addEventListener('click', () => {
eachTodo.classList.toggle('completed');
})
remove.addEventListener('click', () => {
eachTodo.remove();
})
})
.completed p {
text-decoration: line-through;
opacity: 0.8;
}
<input type="text" id="input">
<button type="button" id="addBtn">Add</button>
<div id="plansDiv"></div>
Event delegation
A more concise solution would be to use event delegation and handle all the buttons in a single handler added to the document. Here replacing parentElement with closest('.eachTodo') to avoid the fragility of a specific ancestor depth, and checking which button was clicked using Element.matches().
document.addEventListener('click', (e) => {
if (e.target.matches('button.finish')){
e.target.closest('.eachTodo').classList.toggle('completed');
}
if (e.target.matches('button.remove')){
e.target.closest('.eachTodo').remove();
}
});
const addBtn = document.getElementById('addBtn');
const plansDiv = document.getElementById('plansDiv');
const input = document.getElementById('input');
document.addEventListener('click', (e) => {
if (e.target.matches('button.finish')){
e.target.closest('.eachTodo').classList.toggle('completed');
}
if (e.target.matches('button.remove')){
e.target.closest('.eachTodo').remove();
}
});
addBtn.addEventListener('click', (e) => {
if (input.value === '') {
return;
}
// adding elements to the body;
const eachTodo = document.createElement('div');
eachTodo.classList.add('eachTodo');
const textName = document.createElement('p');
textName.textContent = input.value;
const btns = document.createElement('div');
btns.classList.add('btns');
const finish = document.createElement('button');
finish.classList.add('finish');
finish.textContent = 'Finish';
finish.type = 'button';
const remove = document.createElement('button');
remove.classList.add('remove');
remove.textContent = 'Remove';
remove.type = 'button';
btns.append(finish, remove);
eachTodo.append(textName, btns);
plansDiv.append(eachTodo);
input.value = '';
});
.completed p {
text-decoration: line-through;
opacity: 0.8;
}
<input type="text" id="input">
<button type="button" id="addBtn">Add</button>
<div id="plansDiv"></div>
Note that you can also avoid the necessity of e.preventDefault() by specifying type="button" when you create your buttons. see: The Button element: type

I have to double click the follow button to functioning this is due to using click event 2 time

I am working on follow button and with the help of JavaScript I've come up with the following code.
But the problem is I have to double click the follow button to functioning this is due to using click event 2 time. I am open to better methods of solving this too.
var value = null;
const onClick = (event) => {
// event.target.id
value = event.target.id;
console.log(value);
document.getElementById(`${value}`).addEventListener('click',function(){
// console.log(value.id);
if(this.classList.contains('follow')){
this.classList.remove('follow');
this.innerHTML ="Following";
this.style.backgroundColor = 'green' ;
}else{
this.classList.add('follow');
this.style.backgroundColor = 'rgb(27,18,83)' ;
this.innerHTML="Follow";
}
})
}
window.addEventListener('click', onClick);
There is a double click event. You can check the if it satisfies your requirement.
MDN link - https://developer.mozilla.org/en-US/docs/Web/API/Element/dblclick_event
For multiple buttons with the same function, I would give all of them the same class (e.g. "btn"). Then in JS simply get all of the elements with this class, loop over the HTMLCollection which you would get and assign each element an eventlistener. When you want to change something on the button you have to use event.target in the function:
let buttons = document.getElementsByClassName("btn");
for (btn of buttons) {
btn.addEventListener("click", (event) => {
if(event.target.classList.contains('follow')){
event.target.classList.remove('follow');
event.target.innerHTML ="Following";
event.target.style.backgroundColor = 'green' ;
}else{
event.target.classList.add('follow');
event.target.style.backgroundColor = 'rgb(27,18,83)' ;
event.target.innerHTML="Follow";
}
});
}
<button class="btn">1</button>
<button class="btn">2</button>

How can I write this javascript code cleaner?

all
I am currently practicing my coding skills and am making a simple footer selection webpage.
I have four footers with different looks that are set to "display:none" initially. Then, I have four buttons, each one corresponding to its footer type. When the button is clicked, it displays the footer.
Now I just want to know how do I write a cleaner Javascript than what I currently have. Thank you as always.
var footer1 = document.getElementById('footer1');
var footer2 = document.getElementById('footer2');
var footer3 = document.getElementById('footer3');
var footer4 = document.getElementById('footer4');
var btn1 = document.getElementById('btn1');
var btn2 = document.getElementById('btn2');
var btn3 = document.getElementById('btn3');
var btn4 = document.getElementById('btn4');
btn1.onclick = function(e) {
console.log('You clicked button1');
e.preventDefault();
footer1.style.display = 'block';
footer2.style.display = 'none';
footer3.style.display = 'none';
footer4.style.display = 'none';
}
btn2.onclick = function(e) {
console.log('You clicked button2');
e.preventDefault();
footer2.style.display = 'block';
footer1.style.display = 'none';
footer3.style.display = 'none';
footer4.style.display = 'none';
}
btn3.onclick = function(e) {
console.log('You clicked button3');
e.preventDefault();
footer3.style.display = 'block';
footer2.style.display = 'none';
footer1.style.display = 'none';
footer4.style.display = 'none';
}
btn4.onclick = function(e) {
console.log('You clicked button4');
e.preventDefault();
footer4.style.display = 'block';
footer2.style.display = 'none';
footer3.style.display = 'none';
footer1.style.display = 'none';
}
You can just use Arrays like this:
let buttons = [ 'btn1', 'btn2', 'btn3', 'btn4' ];
let footers = [ 'footer1', 'footer2', 'footer3', 'footer4' ];
buttons.forEach((btn, index) => {
// Please note that you might want to use addEventListener instead of onclick
document.getElementById(btn).addEventListener('click', (event) => {
event.preventDefault();
let button = 'button' + (index + 1);
alert('You clicked ' + button);
footers.forEach((footer, index_f) => {
let f = document.getElementById(footer);
if(index_f === index) {
f.style.display = 'block';
}
else {
f.style.display = 'none';
}
});
});
});
To make things even more interesting, you can play with querySelectorAll and custom attributes. You could, for example, add the classes custom-button to your buttons and custom-footer to your footers, and on each button add a data-footer attribute pointing to the id of the corresponding footer. Then, you could do this:
document.querySelectorAll(".custom-button").forEach((button) => {
button.addEventListener('click', (event) => {
document.querySelectorAll(".custom-footer").forEach(footer => footer.style.display = 'none');
let footer = button.getAttribute("data-footer");
document.getElementById(footer).style.display = 'block';
});
});
Quite shorter.
There is a common way to do this.
Share a class (foo) between all of the elements you want to group into your show/hide radio button-like functionality.
Then use one function, like
function handler = function(e) {
var foos = document.getElementsByClass("foo");
// make all foos display="none"
var target = targets[e.target]; //get the footer to show from the target button
target.style.display = "block";
}
You can use attribute selector to loop over elements.
[attributeName="value"]
^: Means starts with
$: Ends with
This way, your logic is generic and does not requires you to maintain any list of IDs
Idea:
You can create a pattern where every button id will correspond to visibility to necessary footer. Better idea would be to use data attribute(data-<attr name>) but you can work with ids for now.
Loop over all the buttons and add handler using addEventListener. onClick is a property, so assignment will erase/ override previous value. You can either have inline anonymous function or a named function if you have too many buttons.
In this handler, loop over all footers and hide them.
Fetch index using this.id and show necessary footer. Its always better to use classes for such actions instead of setting styles.
var buttons = document.querySelectorAll('[id^="btn"]');
Array.from(buttons, (button) => {
button.addEventListener('click', function(event) {
const footers = document.querySelectorAll('[id^="footer"]');
Array.from(footers, (footer) => footer.style.display = 'none' );
const index = this.id.match(/\d+/)[0];
document.getElementById(`footer${index}`).style.display = 'block';
})
})
div[id^="footer"] {
width: 100px;
height: 100px;
border: 1px solid gray;
margin: 10px;
}
<div id="footer1"> Footer 1 </div>
<div id="footer2"> Footer 2 </div>
<div id="footer3"> Footer 3 </div>
<div id="footer4"> Footer 4 </div>
<button id="btn1"> Button 1 </button>
<button id="btn2"> Button 2 </button>
<button id="btn3"> Button 3 </button>
<button id="btn4"> Button 4 </button>
References:
Attribute Selector - MDN

How do I get my modal to close when a user clicks on the container?

I'm currently studying a full stack course and my modal isn't behaving as expected
I'm a bit lost on what to do as I can't find any documentation anywhere and while clicking on the close button or pressing ESC works, clicking outside of the box doesn't.
The following code is how it has been suggested I approach the issue but, it doesn't work. I've honestly stared at this for about an hour and just can't connect the dots on what is (not) happening? Please excuse all the commenting and additional code as I'm still learning so, it's how I'm able to follow what's going on:
function showModal() {
var $modalContainer = document.querySelector('#modal-container');
$modalContainer.classList.add('is-visible');
}
function hideModal() {
var $modalContainer = document.querySelector('#modal-container');
$modalContainer.classList.remove('is-visible');
}
//modal IFFE
document.querySelector('#modal-button').addEventListener('click', () => {
showModal();
});
//-- show modal --
function showModal(title, text) {
var $modalContainer = document.querySelector('#modal-container');
//Selects the element with the associated id
// Clear all content for the selected element
$modalContainer.innerHTML = '';
var modal = document.createElement('div'); //creates a div element withing selected element
modal.classList.add('modal'); //assigns new class to the div element
// Add the new modal content
var closeButtonElement = document.createElement('button'); //creates the close button
closeButtonElement.classList.add('modal-close'); //assigns a class to the new (close) button
closeButtonElement.innerHTML = "×"; //inserts text within the new(close) button
closeButtonElement.addEventListener('click', hideModal);
var titleElement = document.createElement('h1');
titleElement.innerText = title;
var contentElement = document.createElement('p');
contentElement.innerText = text;
modal.appendChild(closeButtonElement);
modal.appendChild(titleElement);
modal.appendChild(contentElement);
$modalContainer.appendChild(modal);
$modalContainer.classList.add('is-visible');
}
document.querySelector('#modal-button').addEventListener('click', () => {
showModal('PokéMon', 'Here is all of the info about your PokéMon');
});
window.addEventListener('keydown', (e) => {
var $modalContainer = document.querySelector('#modal-container');
if (e.key === 'Escape' && $modalContainer.classList.contains('is-
visible')) {
hideModal();
}
});
$modalContainer.addEventListener('click', (e) => {
var target = e.target;
if (target === $modalContainer) {
hideModal();
}
});
Expected result: User clicks outside of the modal (on the container) and the modal closed.
Current result: No change in state, modal remains active and visible. Only by clicking on the close button (x) or by pressing ESC is the desired result achievable.
By Looking at this code I am not sure what is actually supposed to make the modal visible or hide it. Without access to your css (if you have any). I am assuming that all you are doing is adding and removing the class .is-visible from the #modal-container element.
I would suggest that you apply this class to the modal itself, and then you could toggle this class on and off,
Modify your code to do this by doing something like this (added on top of your code):
function showModal() {
var $modalContainer = document.querySelector('#modal-container');
$modalContainer.classList.add('is-visible');
document.querySelector('.modal').classList.remove('hide-el')
}
function hideModal() {
var $modalContainer = document.querySelector('#modal-container');
$modalContainer.classList.remove('is-visible');
document.querySelector('.modal').classList.add('hide-el')
}
Where hide-el in your css is:
.hide-el {
display: none;
}
You could also modify your code to appply the is-visible class to your modal element. You should always try to attach the class/id to the element you want to manipulate if you have that option.
Or if you do not have access to a css file:
document.querySelector('.modal').style.display = "none"
and
document.querySelector('.modal').style.display = "block"
Also, your code seems very verbose, was this boilerplate part of the assignment?
heres a working example: https://codepen.io/mujakovic/pen/zVJRKG
The code was in the incorrect place in the end and should have looked something like this:
modal.appendChild(closeButtonElement);
modal.appendChild(titleElement);
modal.appendChild(contentImage);
modal.appendChild(contentHeight);
modal.appendChild(contentElement);
$modalContainer.appendChild(modal);
$modalContainer.classList.add('is-visible');
$modalContainer.addEventListener('click', (e) => { //listening for an event (click) anywhere on the modalContainer
var target = e.target;
console.log(e.target)
if (target === $modalContainer) {
hideModal();
}
});
};
window.addEventListener('keydown', (e) => { //listening for an event (ESC) of the browser window
var $modalContainer = document.querySelector('#modal-container');
if (e.key === 'Escape' && $modalContainer.classList.contains('is-visible')) {
hideModal();
}
});
This is because the action was originally being called on page load and targeted within the window instead of being called within the container and being loaded when the modal loads.
Thank for your help

Categories

Resources