I want to toggle a list item to show. when it is clicked I want a tick icon to toggle with the click. It works with 1 <li> item, but when I add another and click that one, the first tick disappears.
How can I get the first tick to remain after the second one is clicked? This is a DOM specific exercise.
I am first setting up an event listener for submit for my form. On submit I am creating a div and an li. Then on click of the li I am adding the icon.
html:
<div class='body-container'>
<p class="subtitle">Please add an item</p>
<form id="myForm" class='form'>
<input type="text">
<button type="submit">submit</button>
</form>
<h2>Items</h2>
<div>
<ol class="item">
</ol>
</div>
<div class="footer">
</div>
</div>
js:
//grab the target from the form submission
const myForm = document.querySelector('#myForm');
const item = document.querySelector('.item');
const ol = document.querySelector('ol');
const i = document.createElement('i')
myForm.addEventListener('submit', (event) => {
event.preventDefault();
const value = event.target[0].value;
const li = document.createElement('li');
const div = document.createElement('div');
li.classList.add('test');
li.addEventListener('click', () => {
div.classList.toggle('strike');
div.appendChild(i);
i.classList.toggle('fas');
i.classList.toggle('fa-check');
})
li.appendChild(document.createTextNode(value));
div.appendChild(li)
ol.appendChild(div)
})
At the moment when I tick the second item the ticks from both li's disappear
This is the way to achieve it, your code was almost right
const myForm = document.querySelector('#myForm');
const item = document.querySelector('.item');
const ol = document.querySelector('ol');
myForm.addEventListener('submit', (event) => {
event.preventDefault();
const value = event.target[0].value;
const li = document.createElement('li');
const div = document.createElement('div');
const i = document.createElement('i');
li.id = `item-${new Date().getTime()}`
li.classList.add('test');
li.addEventListener('click', () => {
div.classList.toggle('strike');
div.appendChild(i);
i.classList.toggle('fas');
i.classList.toggle('fa-check');
})
li.appendChild(document.createTextNode(value));
div.appendChild(li)
ol.appendChild(div)
})
Hi Evandro Cavalcate Santos, I was thinking to just move the line
const i = document.createElement('i')
to the myForm eventListener as you did, because it was created with a global scope and each div would refer to that same global 'i'.
I don't think the 'id' is necessary on each li. Let me know.
Thanks
//grab the target from the form submission
const myForm = document.querySelector('#myForm');
const item = document.querySelector('.item');
const ol = document.querySelector('ol');
myForm.addEventListener('submit', (event) => {
event.preventDefault();
const value = event.target[0].value;
const li = document.createElement('li');
const div = document.createElement('div');
const i = document.createElement('i')
li.classList.add('test');
li.addEventListener('click', () => {
div.classList.toggle('strike');
div.appendChild(i);
i.classList.toggle('fas');
i.classList.toggle('fa-check');
})
li.appendChild(document.createTextNode(value));
div.appendChild(li)
ol.appendChild(div)
})
i.fas:after{
content: "X"
}
.strike{
border:1px solid red;
}
<div class='body-container'>
<p class="subtitle">Please add an item</p>
<form id="myForm" class='form'>
<input type="text">
<button type="submit">submit</button>
</form>
<h2>Items</h2>
<div>
<ol class="item">
</ol>
</div>
<div class="footer">
</div>
</div>
Related
I have 3 buttons and 3 content.
When I click on any button, I want to show the content of that button. When I click on the other button, I want to show the content of that button and delete the content of the other button.
I managed to do this with simple logic, but how can I do it with a for loop?
const content = document.querySelectorAll('.content');
const contentClose = document.querySelector('.close-content');
const btnsOpenContent = document.querySelectorAll('.show-content');
btnsOpenContent[0].addEventListener('click',
function() {
content[1].classList.remove('show-modal');
content[2].classList.remove('show-modal');
content[0].classList.add('show-modal');
}
)
btnsOpenContent[1].addEventListener('click',
function() {
content[0].classList.remove('show-modal');
content[2].classList.remove('show-modal');
content[1].classList.add('show-modal');
}
)
btnsOpenContent[2].addEventListener('click',
function() {
content[0].classList.remove('show-modal');
content[1].classList.remove('show-modal');
content[2].classList.add('show-modal');
}
)
Use simple forEach loops along with classList.toggle(className: string, force: boolean):
const content = document.querySelectorAll('.content');
const contentClose = document.querySelector('.close-content');
const btnsOpenContent = document.querySelectorAll('.show-content');
btnsOpenContent.forEach((btn, index) => {
btn.addEventListener('click', () => {
content.forEach((cnt, idx) => cnt.classList.toggle('show-modal', idx === index))
})
})
I strongly suggest to delegate, which means instead of adding a listener to each and every button, you instead capitalize on the bubbling mechanism of events, listening to clicks instead on just one common ancestor element of all buttons. Then inside the click handler you implement a guard that checks if the click actually came from a button.
Note I added hidden to the last two content to start with content 1
You can also add active to the button that was clicked and remove from the siblings
NOTE: This code does not change no matter how many buttons as long as there is a matching number of content divs.
Also note there is no need for a class to show and hide
Lastly note I implemented your close too
const nav = document.getElementById("nav");
const contents = document.getElementById("contents");
const buttons = nav.querySelectorAll("#nav .show-content");
const contentDivs = contents.querySelectorAll("div.content")
nav.addEventListener("click", e => {
const tgt = e.target; // what was clicked?
if (!tgt.matches(".show-content")) return; // not a button
buttons.forEach((but,i) => contentDivs[i].hidden = but !== tgt); // hide if not the content that belongs to button
})
contents.addEventListener("click", e => {
const tgt = e.target;
if (!tgt.matches(".close")) return; // not a button
tgt.closest("div").hidden = true; // hide the div the close button is in
})
<div id="nav">
<button class="show-content">Show 1</button>
<button class="show-content">Show 2</button>
<button class="show-content">Show 3</button>
</div>
<div id="contents">
<div class="content">Content 1 <button class="close">X</button></div>
<div class="content" hidden>Content 2 <button class="close">X</button></div>
<div class="content" hidden>Content 3 <button class="close">X</button></div>
</div>
The most important thing to note is that querySelectorAll does not return an array, and instead returns a NodeList. ES6 has introduced a handy method that is intended for this exact purpose NodeList.prototype.forEach().
The easiest approach in my experience to create tabbed content is to add some sort of identifier for each ".content" tab on the button triggering the event. the "data" attribute is often used for this, however, there several other options.
An example of your button html using the data attribute would look like the following:
<button class="show-content" data-content="content1">Show content 1</button>
<button class="show-content" data-content="content2">Show content 2</button>
<button class="show-content" data-content="content3">Show content 3</button>
For this technique your content HTML would need an id that matches the identifier used on your buttons:
<div class="content" id="content1">Some content for 1</div>
<div class="content" id="content2">Some content for 2</div>
<div class="content" id="content3">Some content for 3</div>
And the corresponding javascript would look similar to below:
const btnsOpenContent = document.querySelectorAll('.show-content');
btnsOpenContent.forEach((button) => {
button.addEventListener('click', (event) => {
let contentId = event.target.getAttribute('data-content');
let targetContent = document.getElementById(contentId);
// Hide all contents
document.querySelectorAll('content').forEach((element) => {
element.classList.remove('show-modal');
})
// Show selected content
targetContent.classList.add('show-modal');
});
});
This code can even be shortened:
const btnsOpenContent = document.querySelectorAll('.show-content');
btnsOpenContent.forEach((button) => {
button.addEventListener('click', (event) => {
let contentId = event.target.dataset.content;
let targetContent = document.getElementById(contentId);
document.querySelectorAll('content').forEach((element) => {
element.classList.toggle('show-modal', contentId === element.id);
})
});
});
Maybe something like this?
const content = document.querySelectorAll('.content');
const contentClose = document.querySelector('.close-content');
const btnsOpenContent = document.querySelectorAll('.show-content');
for (let i = 0; i <= 2; i++) {
btnsOpenContent[i].onclick = () => {
for (let j = 0; j <= 2; j++) {
i == j ? content[j].classList.add('show-modal') : content[j].classList.remove('show-modal');
}
};
}
I am using jquery to bind a click on a button. In this var testId i am getting the id of the button and var showmoreId is getting the id of a div but the problem is onclick binds with the li so when i click anywhere in li onclick is working but i want it to work on button click and i also want the id of the div on click but do not want li to work on click so what can i do to fix it.
async function userData(userdata) {
let newValue = "";
userdata.forEach(max=> {
let add = `<div class="rb-container">
<ul class="rb">
<li>
<div>
<p>${max.name}</p>
<div>
<p>${max.email}</p>
<button id="showMoreLess_${max.id}" data-order-id="${max.id}">View More</button>
</div>
<div id="showme_${max.id}" class="showBlock" style="display:none";>
<div>
<div>
<p>Quantity:</p>
<p>${max.volume}</p>
</div>
<div>
<p>${max.group_email}</p>
<p>Group_Email</p>
</div>
</div>
</div>
</div>
</li>
</ul>
</div>`
newValue += add ;
let container = document.querySelector('.user-details');
container.innerHTML = newValue;
}
$(document).ready(function() {
$('.rb-container li').unbind('click');
$('.rb-container li').on('click', function () {
var testId = $(this).find('button').attr('id');
var showmoreId = ($(this).find('div > div').next().attr('id'));
viewFullDetails(testId, showmoreId);
});
The .on() function supports an optional selector to handle events on certain child elements.
$('.rb-container li').on('click', 'button', function() {
var testId = $(this).attr('id');
var showmoreId = $(this).closest('li').find('div > div').next().attr('id');
viewFullDetails(testId, showmoreId);
});
Note that I just added the selector 'button' but of course you can use any selector, e.g. #id. I then found the closest li to go from there to get the showmoreId.
Please also consider to use .off() instead of .unbind() as the latter has been deprecated.
const list = document.getElementById('list')
const addtodo = document.getElementById('addtodo')
//const items = document.querySelector('.item')
const deleteItem = document.querySelector('.delete')
addtodo.addEventListener('keypress', submitTodo)
function submitTodo(event) {
if (event.keyCode == 13) {
event.preventDefault()
let value = addtodo.value
let li = document.createElement('li')
li.innerHTML = `
<img class="unchecked" src="icon/unchecked.svg" />
${value}
<img class="delete" src="icon/icons8-multiply-26.png" /> `
list.appendChild(li)
}
}
deleteItem.addEventListener('click', items)
function items(item) {
if (item.target.classList.contains('delete')) {
item.target.parentElement.remove()
}
}
The code above only allows me to delete one item and its the first one on the list
I try to solve it on my own but couldn't any idea whats wrong
When deleteItem.addEventListener('click', items) is ran, it only attaches the eventListener to the elements currently on the DOM - the ones you create dynamically will not have this eventListener
You can use 'event delegation' instead to listen for clicks, and filter theses clicks based on if the click was coming from the correct element
You can read more about event delegation from davidwalsh's blog and this StackOverflow answer
document.addEventListener('click', function(e) => {
if(e.target && e.target.classList.includes('delete')){
e.target.parentElement.remove()
}
});
You could also make use of the elements onclick attribute, and pass this in the parameter of the function call - this way you can access the HTML Element directly from the parameter; this also avoids having to have an eventListener, or using an if to check if it's the correct class / ID
// Add the function to the onclick attribute of the img
<img class="delete" onclick="deleteItem(this)" src="demo.png" />
// in this function, 'item' refers to the DOM Element that was clicked
function deleteItem (item) {
item.parentElement.remove();
}
This code will allow you to remove the first element on ul .
let list = document.getElementById("list"); // ul element
let remove = document.getElementById("remove"); // remove button
// on double click event
remove.onclick = () => {
// remove the first child from the list
list.removeChild(list.firstChild);
}
<ul id="list">
<li>One</li>
<li>Two</li>
<li>three</li>
</ul>
<input type="button" id="remove" value="Remove First"></input>
I am trying to create a visual element where you can add and remove 2 input fields and a p element, while I found a way to do it, While removing them not in chronological order the last div wont be removed and prints me "cant remove of undefied"
I tried doing it in a few ways, through if function, throgh array methods etc... always the same problem
so the Html code goes this way
<main id="mainBlock">
<div class="divBlock">
<input class="name" type="text">
<input class="workingHours" type="text">
<p class="money"></p>
<button class="deleteButton">delete</button>
</div>
<button id="addButton">add</button>
</main>
and the js:
let addButton = document.getElementById('addButton');
let allDivs = document.getElementsByClassName('divBloc');
addButton.onclick = function(){
let deleteButtons = document.querySelectorAll('button.deleteButton');
let allDeleteButtonsArr = Array.from(allDeleteButtons)
allDeleteButtonsArr.forEach(item => {
item.onclick = function(){
let indexNumber = allDeleteButtonsArr.indexOf(item);
allDivs[indexNumber].remove();
};
});
I think i should explain while the onclick function is related to the create button at first. For the purpose of giving you easier time to read I delete all the part where I create all the new p div and input elements when you click on it. because each time you click on add element there is a new index number I thought it will be better to include it inside the addButton onclick fucntion.
Thanks in advance :)
Since you're dynamically appending nodes, and then you wish to remove them, adding/removing event handlers to the delete button might be very annoying.
A better way is to use event delegation by adding the event listener to the container #mainBlock, and when it's called check if the the delete button was called, and if so remove it's parent.
const item = `
<div class="divBlock">
<input class="name" type="text">
<input class="workingHours" type="text">
<p class="money"></p>
<button class="deleteButton">delete</button>
</div>
`;
const container = document.querySelector('#mainBlock');
const addButton = document.querySelector('#addButton');
addButton.addEventListener('click', () => {
addButton.insertAdjacentHTML('beforebegin', item);
});
container.addEventListener('click', e => {
if(!e.target.matches('.deleteButton')) return;
e.target.parentNode.remove();
});
<main id="mainBlock">
<button id="addButton">add</button>
</main>
I want to add a class to an element that shares the same data attribute value using vanilla JS. The class is added on mouseenter.
My current setup only applies the class on hover to the first element and ignores the rest.
let section = document.querySelector('.section');
let links = document.querySelectorAll('.links a');
let triggerVal;
let linkedVal;
links.forEach(function(link, index) {
link.addEventListener('mouseenter', function() {
triggerVal = this.dataset.triggerValue;
linkedVal = section.dataset.linkedValue;
if (linkedVal === triggerVal) {
section.classList.add('is-active');
} else {
section.classList.remove('is-active');
}
});
});
<ul class="links">
<li>
<a data-trigger-value="red" href="#">Red</a>
</li>
<li>
<a data-trigger-value="yellow" href="#">Yellow</a>
</li>
<li>
<a data-trigger-value="blue" href="#">Blue</a>
</li>
</ul>
<div class="wrapper">
<div class="section" data-linked-value="red">
<h2>Red</h2>
</div>
<div class="section" data-linked-value="yellow">
<h2>Yellow</h2>
</div>
<div class="section" data-linked-value="blue">
<h2>Blue</h2>
</div>
</div>
Here's a Codepen: https://codepen.io/abbasarezoo/pen/7378e190ed6ad117faca968b634520b0
I've got a feeling it's to do with the .section element but I've tried a few things and nothing seems to give me what I need.
Any suggestions as to what I need to do to get the rest of the elements working?
You need to change two things:
First, get all sections:
const section = document.querySelectorAll('.section');
Then, inside your handler, you need to iterate over the NodeList returned by querySelectorAll():
for (const section of sections) {
linkedVal = section.dataset.linkedValue;
if (linkedVal === triggerVal) {
section.classList.add('is-active');
} else {
section.classList.remove('is-active');
}
}
This is your new JS:
const sections = document.querySelectorAll('.section');
const links = document.querySelectorAll('.links a');
let triggerVal;
let linkedVal;
links.forEach(function(link, index){
link.addEventListener('mouseenter', (e) => {
triggerVal = e.target.dataset.triggerValue;
for (const section of sections) {
linkedVal = section.dataset.linkedValue;
if (linkedVal === triggerVal) {
section.classList.add('is-active');
} else {
section.classList.remove('is-active');
}
}
});
});
You need to use document.querySelectorAll for sections and then forEach. And use toggle instead of add/remove for this case. https://developer.mozilla.org/en-US/docs/Web/API/Element/classList
let sections = document.querySelectorAll('.section');
let links = document.querySelectorAll('.links a');
let triggerVal;
let linkedVal;
links.forEach(function(link, index) {
link.addEventListener('mouseenter', function() {
triggerVal = this.dataset.triggerValue;
sections.forEach(
section => section.classList.toggle(
'is-active',
section.dataset.linkedValue === triggerValue
)
);
});
});