Jsfiddle Here
const btnTodo = document.querySelector('.btnTodo');
const todoList = document.querySelector('.todoList');
const removeBtn = document.querySelector('.btnRemove');
btnTodo.addEventListener('click', () => {
var todoText = txtTodo.value.trim();
var listItem = document.createElement('p');
listItem.innerHTML = todoText + ' <button class="btn btnRemove"><i class="big fas fa-trash"></i></button>' + '<br>';
todoList.append(listItem);
txtTodo.innerText = null;
});
removeBtn.addEventListener('click', () => {
//WHAT TO DO??
});
I am in high school. I wanted to include a remove functionality to my todo app. I made this by the knowledge given to me by my school.
Okay so there are various ways of doing this, here's one of the ways:
let txtTodo = document.querySelector('.txtTodo');
let btnTodo = document.querySelector('.btnTodo');
let todoList = document.querySelector('.todoList');
btnTodo.addEventListener('click', () => {
let todoText = txtTodo.value.trim();
let listItem = document.createElement('p');
listItem.innerHTML = todoText + ' <button class="btn btnRemove"><i class="big fas fa-trash"></i></button>' + '<br>';
todoList.append(listItem);
txtTodo.innerText = null;
listItem.querySelector('.btnRemove').addEventListener("click", () => {
listItem.remove()
});
});
Fiddle
One thing to keep in mind is that storing all remove buttons in a variable beforehand (as you attempted in line 3) isn't going to work because is isn't a live collection and thus when you append new buttons in the DOM then the stored collection would become stale. Hence it's a good idea to add event listeners to newly appended elements as I did above.
Also you should be doing txtTodo.value = ""; instead of txtTodo.innerText = null; to clear the input.
Good luck!
Related
I am looking to add draggable functionality to my todo list project,
I am trying to use a forEach loop to loop over my elements and for now just change the opacity of each div when dragged. However I can not find a way for this to work on elements which I have created, the draggable attribute is added but they dont work with the loop.
function addTodo() {
const todoDiv = document.createElement('div')
todoDiv.classList.add('todo')
const newButton = document.createElement('button');
newButton.classList.add('todo-button')
const newLine = document.createElement('div')
newLine.classList.add('line')
const newTodo = document.createElement('li');
newTodo.innerText = todoInput.value
newTodo.classList.add('todo-text')
const newDelete = document.createElement('button');
newDelete.innerHTML = '<i class="fas fa-times "></i>'
newDelete.classList.add('delete-todo')
todoList.appendChild(todoDiv)
todoDiv.appendChild(newButton)
todoDiv.appendChild(newLine)
newLine.appendChild(newTodo)
newLine.appendChild(newDelete)
var att = document.createAttribute("draggable");
att.value = "true";
todoDiv.setAttributeNode(att);
}
todo.forEach(todo => {
todo.addEventListener('dragstart', () => {
todo.classList.add('dragging')
})
})
TLDR
Here is a fiddle, when you create a new todo item it doesnt seem to be picked up by the forEach loop and apply the class
Any help would be much appreciated!
I am using a forEach loop and creating a link based on a condition. However, the problem I am facing is that the event listener is only added to the last item. I know this question has been asked before and I know the reason for the issue is Hoisting and I should use closure. I have tried to do that but I do not think I am doing it correctly. I would really appreciate any help as I am kind of stuck. Below are some snippets from my code (I have deleted some pieces from the code for the purpose of the question):
function edit(post, post_id){
alert(post_id);
}
function load_posts(event) {
fetch(`/posts?start=${start}&end=${end}`)
.then(response => response.json())
.then(data => {
document.querySelector('#posts').innerHTML = "<div></div>";
let c = 0;
data.forEach(i => {
if (c === data.length - 1){
return;
};
document.querySelector('#posts').innerHTML += `<div class = 'border' >`
let current_user = document.getElementById("logged_in").innerHTML;
document.querySelector('#posts').innerHTML += `<div class="row" ><div class="col-2"
id = border${i.id}></div>
</div></div>`;
if (i.user_id === parseInt(current_user)){
let element = document.createElement('a');
element.innerHTML = "Edit";
element.setAttribute('data-id', `${i.id}`);
element.setAttribute('data-post', `${i.id}`);
element.setAttribute('href', `#${i.id}`);
element.addEventListener('click', function()
{edit(this.dataset.id,this.dataset.post);});
console.log(element);
document.querySelector(`#border${i.id}`).append(element);
};
}
c++;
});});
Assigning to the innerHTML of an element will corrupt any existing listeners its children have:
document.querySelector('button').addEventListener('click', () => {
console.log('clicked');
});
container.innerHTML += '';
// click listener doesn't work anymore now
<div id="container">
<button>click</button>
</div>
Create the element with document.createElement instead of concatenating an HTML string:
const currentUserId = Number(document.getElementById("logged_in").textContent);
data.slice(0, data.length - 1).forEach((i, index) => {
const newPost = document.querySelector('#posts').appendChild(document.createElement('div'));
newPost.className = 'border';
newPost.innerHTML = `<div class="row" ><div class="col-2"
id = border${i.id}></div>
</div>`;
if (i.user_id === currentUserId) {
let element = document.createElement('a');
element.textContent = "Edit";
element.dataset.id = i.id;
element.dataset.post = i.id;
element.href = `#${i.id}`;
element.addEventListener('click', function() {
edit(this.dataset.id, this.dataset.post);
});
document.querySelector(`#border${i.id}`).append(element);
}
});
I'd also highly recommend changing your i variable name to something else - i is almost always used to indicate the index that you're iterating over. The element being iterated over should not be named i - call it something like postData instead, to avoid confusion.
I am making a todo list. Here is my function that builds my todo list item:
function createTodoElement(todo) {
//todo div
const todoDiv = document.createElement('div');
todoDiv.classList.add('todos');
//complete btn
const completeBtn = document.createElement('button');
completeBtn.setAttribute('data-id', todo.id);
completeBtn.classList.add('complete-btn');
completeBtn.innerText = ('o(-_-)o');
completeBtn.onclick = completeTodo;
//todo content
const todoContent = document.createElement('div');
todoContent.innerText = todo.content;
todoContent.classList.add('todo-content');
//delete button
const deleteBtn = document.createElement('button');
deleteBtn.setAttribute('data-id', todo.id);
deleteBtn.classList.add('todo-delete-btn');
deleteBtn.innerText = 'X';
deleteBtn.onclick = deleteTodo;
todoDiv.appendChild(completeBtn);
todoDiv.appendChild(todoContent);
todoDiv.appendChild(deleteBtn);
return todoDiv;
}
and I am trying to update the todo item's content to say 'done' and update the completed status as 'true' but it doesn't seem to be totally working out for me. here is my function:
function completeTodo(e) {
const btn = e.currentTarget;
btn.innerText = '\\(^_^)/';
let targetId = (btn.getAttribute('data-id'));
console.log(targetId);
let hasId = ls.getTodoList();
let item = hasId.find(obj => obj.id == targetId);
item.content = "done";
item.completed = 'true';
localStorage.setItem('content', JSON.stringify(item.content));
}
my object looks somewhat like this: array[{id: 23435352, content: 'make soup', completed: 'false'}, {id:48283749, content: 'study', completed: 'false'}]
everything seems to be working okay but the updating the new data to locale storage. I also want to save the new button inner text. what do I need to change in my function to make this happen?
I'm creating a basic to do list in Vanilla JS, I'm using Handlebars to keep the HTML & JS separate.
Everything was going fine till I came to the delete method. Because my delete button is inside my HTML and not created inside my JS I'm finding it hard to select and delete items from the array.
I thought I'd found a way around it by looping over them but the issue with this is it tries to grab the buttons on page load, and so it returns always an empty array as on page load there are no delete buttons as no to do has been added at that point.
I've also tried putting the delete method inside the add method to counter this but this also presented issues.
Simply, can someone give me an example of a working delete method that removes the relevant item from the array using splice.
Cheers
HTML
<input id="add-to-do-value" type="text" placeholder="Add to do">
<button id="add-to-do">Add</button>
<div id="to-do-app"></div>
<script type="text/javascript" src="js/handlebars.js"></script>
<script id="to-do-template" type="text/template">
<ul>
{{#this}}
<div>
<li id={{id}}>
{{value}}
<button class="delete-btn" id={{id}}>Delete</button>
</li>
</div>
{{/this}}
</ul>
</script>
<script type="text/javascript" src="js/app.js"></script>
JS
(function() {
// Data array to store to dos
var data = [];
// Cache dom
var toDoApp = document.getElementById('to-do-app');
var toDoTemplate = document.getElementById('to-do-template');
var addToDo = document.getElementById('add-to-do');
var addToDoValue = document.getElementById('add-to-do-value');
var toDoTemplate = Handlebars.compile(toDoTemplate.innerHTML);
// Render HTML
var render = function() {
toDoApp.innerHTML = toDoTemplate(data);
}
// Add to dos
var add = function() {
var toDoValue = addToDoValue.value;
if(toDoValue) {
var toDoObj = {
value: toDoValue,
id: Date.now(),
}
data.push(toDoObj);
}
render();
}
// Delete to dos
var deleteBtn = document.querySelectorAll('.delete-btn');
for(i=0; i<deleteBtn.length; i++) {
deleteBtn[i].addEventListener("click", function(){
for(j=0; j<data.length; j++) {
if(data[j].id == this.id) {
data.splice(data[j], 1);
render();
}
}
});
}
// Bind events
addToDo.addEventListener("click", add);
})();
The fact that you're using Handlebars makes the whole thing unnecessary complex. I would suggest that you don't use innerHTML, but other parts of the DOM API instead to be able to easily access the elements you need. For more complex todo items, I would consider using <template>s.
Anyway, you have to bind the event listener for removing the item when you create the new item (i.e. in the add function):
var todos = [];
var input = document.querySelector('input');
var addButton = document.querySelector('button');
var container = document.querySelector('ul');
var add = function () {
var content = input.value;
input.value = '';
var id = Date.now();
var li = document.createElement('li');
li.appendChild(document.createTextNode(content));
var button = document.createElement('button');
button.textContent = 'Delete';
button.addEventListener('click', remove.bind(null, id));
li.appendChild(button);
todos.push({ content, id, element: li });
container.appendChild(li);
};
var remove = function (id) {
var todo = todos.find(todo => todo.id === id);
container.removeChild(todo.element);
todos = todos.filter(t => t !== todo);
};
addButton.addEventListener('click', add);
<input type="text" placeholder="Add to do">
<button>Add</button>
<ul></ul>
I'm trying to create multiple lists on the same page with multiple "Add" buttons.
Entering text in text field 1 and clicking button1 should only add things into list1 (Monday). But button1 is adding text from text field 2 into the last loaded JS which is list2 (Tuesday). There are 8 lists in total I'm only trying to get the first 2 lists working atm.
Here's the JSFiddle: https://jsfiddle.net/sarwech/vrn5s2ns/4/
This looks like there isn't proper closure and I've considered creating separate, local functions but I'm not too sure...
The below worked when I only wanted to add items into each list:
document.getElementById("add1").onclick = function() {
var node = document.createElement("Li");
var text = document.getElementById("user_input1").value;
var textnode=document.createTextNode(text);
node.appendChild(textnode);
document.getElementById("list_item1").appendChild(node);
localStorage.setItem('monday', JSON.stringify(list_item1));
show();
return false; }
document.getElementById("add2").onclick = function() {
var node = document.createElement("Li");
var text = document.getElementById("user_input2").value;
var textnode=document.createTextNode(text);
node.appendChild(textnode);
document.getElementById("list_item2").appendChild(node); }
Appreciate any help!
This happens because you have two add function definitions. Because of hoisting the latter definition will suppress the former one. That's why you are actually binding tuesday click handler to both tuesday's and monday's add buttons.
You should consider rewriting your code to a reusable component, something like
function DayMeals(id, title) {
var self = this;
this.id = id;
this.$el = document.createElement('div');
this.$el.classList.add('row');
this.$el.innerHTML = '<h4>' + title + '</h4><input type="text"/><button>Add</button><ol></ol>';
this.$list = this.$el.querySelector('ol');
this.$input = this.$el.querySelector('input');
this.$addButton = this.$el.querySelector('button');
this.meals = this.getFromStorage();
this.updateView();
this.$addButton.addEventListener('click', function() {
self.add(self.$input.value);
});
this.$el.addEventListener('click', function(e) {
if (e.target.dataset.remove) {
self.remove(e.target.dataset.remove)
}
});
}
DayMeals.prototype.getFromStorage = function() {
var storage = localStorage.getItem(this.id);
return storage ? JSON.parse(storage) : [];
};
DayMeals.prototype.putToStorage = function() {
localStorage.setItem(this.id, JSON.stringify(this.meals));
};
DayMeals.prototype.add = function(meal) {
this.meals.push(meal);
this.putToStorage();
this.updateView();
};
DayMeals.prototype.remove = function(index) {
this.meals.splice(index, 1);
this.putToStorage();
this.updateView();
};
DayMeals.prototype.updateView = function() {
var listContent = '';
this.meals.forEach(function(meal, i) {
listContent += '<li>' + meal + '<button data-remove="' + i + '">x</button></li>';
});
this.$list.innerHTML = listContent;
};
fiddle
You have two functions named 'add', and last one is overriding first one. Name them properly, i.e. mondayAdd, tuesdayAdd. Same thing goes for other functions that share the same name: 'show', 'remove'.