I am currently trying to delete li by clicking deleteButton.
This is my full JS code.
const form = document.querySelector('#form');
const savedList = document.getElementById('savedList');
const doneList = document.getElementById('doneList');
form.addEventListener('submit', (e) => {
const li = document.createElement('li');
li.innerText = document.getElementById('input').value;
li.id = li.innerText;
li.isDone = false;
li.isDone ? doneList.append(li) : savedList.append(li);
const deleteButton = document.createElement('button');
deleteButton.innerText = 'x';
deleteButton.id = 'deleteButton';
li.append(deleteButton);
li.addEventListener('click', () => {
li.isDone ? (li.isDone = false) : (li.isDone = true);
li.isDone ? doneList.append(li) : savedList.append(li);
});
deleteButton.addEventListener('click', function(event) {
const targetToDo = event.currentTarget.parentNode;
console.log(targetToDo.parentNode);
targetToDo.parentNode.remove(targetToDo);
targetToDo.remove();
});
e.preventDefault();
});
<form id="form">
<input id="input" type="text" />
<button type="submit">Submit</button>
</form>
<ul id="savedList"></ul>
<ul id="doneList"></ul>
However, other lis except targetToDo are being deleted. I've tried debugging with console.log but targetToDo and its parentNode is okay. I can see li for a targetToDo and ul for its parentNode. How can I fix this? Thanks in advance.
Try this...
deleteButton.addEventListener('click', function(event) {
const targetToDo = event.currentTarget.parentNode;
console.log(targetToDo.parentNode);
setTimeout(() => li.remove(), 0); // The li that will be removed also has the delete button as child and we are inside the event handler of that child. A setTimeout() works here
});
Illustration
Hopefully, following rudimentary setup along with the code in OP for illustration is closer to the needs of the OP.
const form = document.querySelector('#form');
const savedList = document.querySelector("#saved-list");
const doneList = document.querySelector("#done-list");
form.addEventListener('submit', (e) => {
const li = document.createElement('li');
li.innerText = document.getElementById('input').value;
li.id = li.innerText;
li.isDone = false;
li.isDone ? doneList.append(li) : savedList.append(li); //<--- This condition check is insignificant as we are setting it to false in the previous statement
const deleteButton = document.createElement('button');
deleteButton.innerText = 'x';
deleteButton.id = 'deleteButton';
li.append(deleteButton);
li.addEventListener('click', () => {
li.isDone ? (li.isDone = false) : (li.isDone = true);
li.isDone ? doneList.append(li) : savedList.append(li);
});
deleteButton.addEventListener('click', function(event) {
const targetToDo = event.currentTarget.parentNode;
console.log(targetToDo.parentNode);
// targetToDo.parentNode.remove(targetToDo);
// targetToDo.parentNode.remove();
// targetToDo.remove();
setTimeout(() => li.remove(), 0);
});
e.preventDefault();
});
<form id="form">
<input id="input" type="text" />
<button type="submit">Submit</button>
</form>
<ul id="saved-list"></ul>
<ul id="done-list"></ul>
WYSIWYG => WHAT YOU SHOW IS WHAT YOU GET
You need to use event.stopPropagation() at the end of your deleteButton.addEventListener. It will fix your problem.
What is happening with your code is as below.
When you click on x button it will invoke click event for that button as well as click event of li.
So, first it will invoke click event of x & from deleteButton.addEventListener it will remove your li.
Then it will invoke click event of li and as per code from li.addEventListener it will move li to other ul.
The stopPropagation() method of the Event interface prevents further propagation of the current event in the capturing and bubbling phases.
So if you use event.stopPropagation() in your deleteButton.addEventListener then it will prevent further invoke of li click event and it won't be able to add li again to other ul.
Try code below.
const form = document.querySelector('#form');
const savedList = document.querySelector("#saved-list");
const doneList = document.querySelector("#done-list");
form.addEventListener('submit', (e) => {
const li = document.createElement('li');
li.innerText = document.getElementById('input').value;
li.id = li.innerText;
li.isDone = false;
li.isDone ? doneList.append(li) : savedList.append(li);
const deleteButton = document.createElement('button');
deleteButton.innerText = 'x';
deleteButton.id = 'deleteButton';
li.append(deleteButton);
li.addEventListener('click', () => {
li.isDone ? (li.isDone = false) : (li.isDone = true);
li.isDone ? doneList.append(li) : savedList.append(li);
});
deleteButton.addEventListener('click', function(event) {
const targetToDo = event.currentTarget.parentNode;
console.log(targetToDo.parentNode);
// targetToDo.parentNode.remove(targetToDo);
targetToDo.remove();
// Add below line.
event.stopPropagation();
});
e.preventDefault();
});
<form id="form">
<input id="input" type="text" />
<button type="submit">Submit</button>
</form>
<ul id="saved-list"></ul>
<ul id="done-list"></ul>
Related
I would imagine that this is a very simple code solution to fix but I haven't managed to get it functional.
To give you some perspective, what I currently have done is a form formatting with intl tel input and then I have included the following code(which works great!) inorder to validate the input;
<span id="valid-msg" class="hide">✓Valid number</span>
<span id="error-msg" class="hide"></span>
<style>
.hide {
display: none;
}
#valid-msg {
color: #2b9348;
}
#error-msg {
color: #C31014;
}
<style>
<!--for validation-->
<script>
var input = document.querySelector("#phone"),
errorMsg = document.querySelector("#error-msg"),
validMsg = document.querySelector("#valid-msg");
var updateInputValue = function (event) {
dialCode.value = "+" + iti.getSelectedCountryData().dialCode;
};
input.addEventListener('input', updateInputValue, false);
input.addEventListener('countrychange', updateInputValue, false);
var errorMap = ["Invalid number", "Invalid country code", "Too short", "Too long", "Invalid number"];
var reset = function() {
input.classList.remove("error");
errorMsg.innerHTML = "";
errorMsg.classList.add("hide");
validMsg.classList.add("hide");
};
input.addEventListener('blur', function() {
reset();
if (input.value.trim()) {
if (iti.isValidNumber()) {
validMsg.classList.remove("hide");
} else {
input.classList.add("error");
var errorCode = iti.getValidationError();
errorMsg.innerHTML = errorMap[errorCode];
errorMsg.classList.remove("hide");
}
}
});
input.addEventListener('change', reset);
input.addEventListener('keyup', reset);
</script>
What I'm looking to do is change the style of the submit button if the phone number is valid, I thought this might be done by checking if the #valid-msg span was visible or didn't have a class.
Here is what I have tried:
<script>
document.addEventListener('DOMContentLoaded', () => {
const phoneInput = document.querySelector('#phone');
const validMsg = document.querySelector('#valid-msg');
const targetFormButton = document.querySelector('#form-submit');
if (!phoneInput || !targetFormButton || !validInput) return;
phoneInput.addEventListener('input', () => {
const isValid = validMsg.className !== 'hide';
targetFormButton.classList[isValid ? 'remove' : 'add']('invalid');
targetFormButton.classList[isValid ? 'add' : 'remove']('valid');
});
});
</script> ```
If anyone have suggestions they would be greatly appreciated!
If your validator works correctly, then what you need is a conditional formatting via class toggle:
const btnSubmit = document.querySelector('#form-submit')
const phoneInput = document.getElementById('phone')
const form = document.getElementById('form')
// simplified validation: valid if more
// than 3 characters long
const validator = (val) => {
if (val.length < 3) return false
return true
}
// general function to change classes (add & remove)
// on an element (el)
const validClassToggle = (addClass, removeClass) => (el) => {
el.classList.add(addClass)
el.classList.remove(removeClass)
}
// creating setInvalid & setValid functions
const setInvalid = validClassToggle('invalid', 'valid')
const setValid = validClassToggle('valid', 'invalid')
phoneInput.addEventListener('input', function(e) {
validator(e.target.value) ?
setValid(btnSubmit) :
setInvalid(btnSubmit)
})
form.addEventListener('submit', function(e) {
e.stopPropagation()
e.preventDefault()
if (validator(phoneInput.value)) {
console.log("form submitted")
} else {
console.log("form not submitted")
}
})
.valid {
background-color: green;
color: white;
}
.invalid {
background-color: red;
color: white;
}
<form id="form">
<input id="phone" type="text" /><br />
<button id="form-submit" type="submit">SUBMIT</button>
</form>
The loop works only once and then nothing happens. I have three testimonials, and can go only once forward or backwords.Thanks for help!
const nextBtn = document.querySelector(".next-btn");
const prevBtn = document.querySelector(".prev-btn");
const testimonials = document.querySelectorAll(".testimonial");
let index = 0;
window.addEventListener("DOMContentLoaded", function () {
show(index);
});
function show(index) {
testimonials.forEach((testimonial) => {
testimonial.style.display = "none";
});
testimonials[index].style.display = "flex";
}
nextBtn.addEventListener("click", function () {
index++;
if (index > testimonials.length - 1) {
index = 0;
}
show(index);
});
prevBtn.addEventListener("click", function () {
index--;
if (index < 0) {
index = testimonials.length - 1;
}
show(index);
});
I would use a "hidden" class to hide the non-active testimonials instead of manipulating the element's style in-line. Also, your navigation logic can be simplified to a modulo operation.
The code your originally posted seemed to work out well, but it seems to cluttered with redundancy (code reuse). It also lacks structural flow (readability).
const
modulo = (n, m) => (m + n) % m,
moduloWithOffset = (n, m, o) => modulo(n + o, m);
const
nextBtn = document.querySelector('.next-btn'),
prevBtn = document.querySelector('.prev-btn'),
testimonials = document.querySelectorAll('.testimonial');
let index = 0;
const show = (index) => {
testimonials.forEach((testimonial, currIndex) => {
testimonial.classList.toggle('hidden', currIndex !== index)
});
}
const navigate = (amount) => {
index = moduloWithOffset(index, testimonials.length, amount);
show(index);
}
// Create handlers
const onLoad = (e) => show(index);
const onPrevClick = (e) => navigate(-1);
const onNextClick = (e) => navigate(1);
// Add handlers
window.addEventListener('DOMContentLoaded', onLoad);
nextBtn.addEventListener('click', onNextClick);
prevBtn.addEventListener('click', onPrevClick);
.testimonial {
display: flex;
}
.testimonial.hidden {
display: none;
}
<div>
<button class="prev-btn">Prev</button>
<button class="next-btn">Next</button>
</div>
<div>
<div class="testimonial">A</div>
<div class="testimonial">B</div>
<div class="testimonial">C</div>
<div class="testimonial">D</div>
<div class="testimonial">E</div>
<div class="testimonial">F</div>
</div>
I am a newbie to js and I would like to know how you can use background from javascript let's say I can set the color of a button by doing this:
const btnPulsado = (e) =>{
const btn= e.target;
btn.style.backgroundColor= 'red';
can I do it somehow with backgroundImage?
You can use
e.target.style.backgroundColor = __ANY_COLOR_CODE;
function genRanHex(size) {
return [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join('')
}
const btn = document.querySelector("button");
btn.addEventListener("click", e => {
e.target.style.backgroundColor = `#${genRanHex(6)}`;
})
<button> click me to change color </button>
After reading the title, you might have known the problem. Let me elaborate: when I add an element using JavaScript, I can't do anything with that element. When the element is clicked, the element is supposed to do a certain function, but when I add the new element, just does nothing.
Code:
<div class="progress-bar">
<div class="progress-bar-title">Progress:</div>
<div class="progress-bar-outline">
<div class="progress-bar-percentage"></div>
</div>
</div>
<div class="list" id="listSection">
<ul>
<li>one</li>
<li>two</li>
<li>test</li>
</ul>
</div>
<div class="new-button">Create</div>
<div class="new-section">
<input type="text" class="text-box" placeholder="Name for this card">
</div>
//creates a new element
newText.addEventListener("keyup", (event) => {
if (newText.value != "") {
if (event.keyCode === 13) {
let list_section = document.getElementById("listSection");
let name = newText.value;
let new_li = document.createElement("li");
new_li.innerHTML = name;
list_section.querySelector("ul").appendChild(new_li);
let divs = document.querySelectorAll("div");
newSection.classList.remove("opened");
divs.forEach((div) => {
if (div != newSection) {
div.style.transition = "all 0.5s ease";
div.style.filter = "";
}
});
}
}
});
//looping through each list to add that function
list_Sections.forEach((list) => {
totalListCount++;
list.addEventListener("click", () => {
list.classList.toggle("checked"); //this function doesn't apply to the newly created element
if (!list.classList.contains("checked")) {
listCompleted--;
} else {
listCompleted++;
}
average = (listCompleted / totalListCount) * 500;
percentage.style.width = average;
});
});
Ask you have any questions about this topic.
Thanks for the help!
Whenever a new element is added, your code should attach the event listener to that new element. Try nesting your the code related to .addEventListner() inside the keyup event listener code.
newText.addEventListener("keyup", (event) => {
if (newText.value != "") {
if (event.keyCode === 13) {
let list_section = document.getElementById("listSection");
let name = newText.value;
let new_li = document.createElement("li");
new_li.innerHTML = name;
list_section.querySelector("ul").appendChild(new_li);
let divs = document.querySelectorAll("div");
newSection.classList.remove("opened");
divs.forEach((div) => {
if (div != newSection) {
div.style.transition = "all 0.5s ease";
div.style.filter = "";
}
});
//looping through each list to add that function
list_Sections.forEach((list) => {
totalListCount++;
list.addEventListener("click", () => {
list.classList.toggle("checked");
if (!list.classList.contains("checked")) {
listCompleted--;
} else {
listCompleted++;
}
average = (listCompleted / totalListCount) * 500;
percentage.style.width = average;
});
});
}
}
});
im building a to-do list but cant figure out how to keep my array values that have line-through decoration.
the moment render method is called, the array is built from the start. means that if i delete an li, all other li that have been marked by the checkbox with a line-through, losing the decoration.
what can i do to keep the line-through ?
i tried so far in the markTask method to replace the original value with the value that have line-through on it but it didn't work.
basically what im trying to accomplish is by inserting the value with line-through, to be able to check if this value have the line-through style and after the render to be able to keep the checked checkboxes as checked.
my code so far:
class Todo {
constructor() {
this.input = document.getElementById("input");
this.ul = document.getElementById("ul");
this.form = document.getElementById("form");
this.tasks = [];
this.registerEvent();
}
registerEvent() {
this.form.addEventListener("submit", (event) => {
event.preventDefault();
this.createTask(this.input.value);
this.form.reset();
});
}
createTask(task) {
if (task.trim().length === 0) {
return;
}
this.tasks.push(task);
this.render();
}
deleteTask(task) {
const myTask = task.target;
const parent = myTask.parentNode;
const taskToRemove = parent.childNodes[1].textContent;
const index = this.tasks.indexOf(taskToRemove);
this.tasks.splice(index, 1);
this.render();
}
markTask(task) {
const myTask = task.target;
const parent = myTask.parentNode;
if (myTask.checked) {
parent.style.textDecoration = "line-through";
} else {
parent.style.textDecoration = "none";
}
}
render() {
this.ul.innerHTML = "";
this.tasks.forEach((task) => {
const li = document.createElement("li");
const cb = document.createElement("input");
cb.type = "checkbox";
cb.addEventListener("click", (e) => {
this.markTask(e);
});
li.appendChild(cb);
li.append(document.createTextNode(task));
const btn = document.createElement("button");
li.appendChild(btn);
btn.textContent = "Delete";
btn.classList.add("remove");
btn.addEventListener("click", (e) => {
this.deleteTask(e);
});
this.ul.appendChild(li);
});
}
}
new Todo();
<form id="form">
<input id="input" />
<button id="add">Add</button>
</form>
<ul id="ul">
</ul>
it's because you're not tracking which tasks are done and you're just pushing strings. for your createTask method you need to push an object with a done property to indicate which tasks have been done like so
createTask(task) {
if (task.trim().length === 0) {
return;
}
this.tasks.push({title: task, done: false});
this.render();
}
update your render to account for tasks already done
render() {
this.ul.innerHTML = "";
this.tasks.forEach((task) => {
const li = document.createElement("li");
const cb = document.createElement("input");
cb.type = "checkbox";
cb.addEventListener("click", (e) => {
this.markTask(e);
});
li.appendChild(cb);
li.append(document.createTextNode(task.title));
const btn = document.createElement("button");
li.appendChild(btn);
btn.textContent = "Delete";
btn.classList.add("remove");
btn.addEventListener("click", (e) => {
this.deleteTask(e);
});
this.ul.appendChild(li);
if (task.done) {
cb.checked = true;
li.style.textDecoration = "line-through";
} else {
cb.checked = false;
li.style.textDecoration = "none";
}
});
}
in your constructor update your tasks variable to see this in effect
constructor() {
this.input = document.getElementById("input");
this.ul = document.getElementById("ul");
this.form = document.getElementById("form");
this.tasks = [{title: 'mill', done: true}, {title: 'jus', done: false}];
this.registerEvent();
}
hope you get the general idea. I won't do the entire implementation on markTask as this should be enough to give you a view of what the solution should be. good luck.
If I may, I have revised your code a bit.
The technique you need is event delegation:
any click on a child element is also a click on its parent elements. we plas the event listener on the parent and we see on which child element it occurred.
In your case, this only makes one event listerner for all your 'remove' buttons.
the other idea is not to ignore the DOM, it also keeps the list of tasks, you don't need to keep them in a table in memory, this is redundant.
here is the code: css is also helfull
class Todo
{
constructor()
{
this.form = document.getElementById('todo-form')
this.liste = document.getElementById('todo-list')
this.form.onsubmit = e => this.addTask(e)
this.liste.onclick = e => this.delTask(e)
}
addTask(e)
{
e.preventDefault()
if (this.form.task.value.trim() === '') return
let li = document.createElement('li')
, cb = document.createElement('input')
, sp = document.createElement('span')
, bt = document.createElement('button')
;
cb.type = 'checkbox'
sp.textContent = this.form.task.value
bt.textContent = 'Delete'
bt.className = 'remove'
li.appendChild(cb)
li.appendChild(sp)
li.appendChild(bt)
this.liste.appendChild(li)
this.form.reset()
}
delTask(e)
{
if (!e.target.matches('button.remove')) return // reject others clicks
e.target.closest('li').remove()
}
}
new Todo();
#todo-list li > span {
display : inline-block;
background-color : whitesmoke;
width : 20em;
}
#todo-list li input[type=checkbox]:checked + span {
text-decoration : line-through;
}
#todo-list li button.remove {
font-size: .6em;
}
<form id="todo-form">
<input name="task">
<button type="submit">Add</button>
</form>
<ul id="todo-list"></ul>
As you can see this code is shorter. You can also use a IIFE unstead of a class, like that :
(function() // IIFE
{
let form = document.getElementById('todo-form')
, liste = document.getElementById('todo-list')
;
form.onsubmit = e => // addTask
{
e.preventDefault()
if (form.task.value.trim() === '') return
let li = document.createElement('li')
, cb = document.createElement('input')
, sp = document.createElement('span')
, bt = document.createElement('button')
;
cb.type = 'checkbox'
sp.textContent = form.task.value
bt.textContent = 'Delete'
bt.className = 'remove'
li.appendChild(cb)
li.appendChild(sp)
li.appendChild(bt)
liste.appendChild(li)
form.reset()
}
liste.onclick = e => // delTask
{
if (!e.target.matches('button.remove')) return // reject others clicks
e.target.closest('li').remove()
}
}
)()
btTaskList.onclick = e =>
{
let tasks = [...document.querySelectorAll('#todo-list li')].map(li=>
{
let val = li.querySelector('span').textContent
, chk = li.querySelector('input[type=checkbox]').checked
;
return {val,chk}
})
console.clear()
console.log( tasks )
}
#todo-list li > span {
display : inline-block;
background-color : whitesmoke;
width : 20em;
}
#todo-list li input[type=checkbox]:checked + span {
text-decoration : line-through;
}
#todo-list li button.remove {
font-size: .6em;
}
<form id="todo-form">
<input name="task">
<button type="submit">Add</button>
</form>
<ul id="todo-list"></ul>
<button id="btTaskList">get task list</button>
I also added a get task list button...
After marking an element you are changing only the stayle and atrribute of element. But after delete you recreate with render whole list and in render you are not rendereing checked parameter.
Your render should be:
render() {
this.ul.innerHTML = "";
this.tasks.forEach((task) => {
const li = document.createElement("li");
const cb = document.createElement("input");
cb.type = "checkbox";
cb.addEventListener("click", (e) => {
this.markTask(e);
});
li.appendChild(cb);
// missed rendering checked
if (task.checked) {
li.style.textDecoration = "line-through";
cb.checked = 'checked';
}
li.append(document.createTextNode(task));
const btn = document.createElement("button");
li.appendChild(btn);
btn.textContent = "Delete";
btn.classList.add("remove");
btn.addEventListener("click", (e) => {
this.deleteTask(e);
});
this.ul.appendChild(li);
});
}