Why I get duplicated todos in todo list? - javascript

This is the js code
let form = document.getElementById('todoForm');
let input = document.getElementById('todoInput');
let btn = document.getElementById('btn');
let todos = [];
const loadTodos = () => {
let parent = document.getElementById('todoList');
todos.forEach(todo => {
let newLi = document.createElement('li');
newLi.innerHTML = `<li>${todo.text}</li>`
parent.appendChild(newLi);
})
}
btn.addEventListener('click', (e) => {
e.preventDefault();
let text = input.value;
let todo = {
id: todos.length + 1,
text: text,
complete: false,
}
todos.push(todo);
loadTodos();
})
window.onload = () => {
loadTodos();
}
When I add a todo for the first time its ok, but the seconed time will print the first todo again include the seconed.
example:
first todo
2.first todo
3.seconed todo

You should make another function to handle single todo added, below is your updated code
let form = document.getElementById('todoForm');
let input = document.getElementById('todoInput');
let btn = document.getElementById('btn');
let todos = [];
const loadTodos = () => {
let parent = document.getElementById('todoList');
todos.forEach(todo => {
let newLi = document.createElement('li');
newLi.innerHTML = `<li>${todo.text}</li>`
parent.appendChild(newLi);
})
}
const renderNewToDo = (todo) => {
let parent = document.getElementById('todoList');
let newLi = document.createElement('li');
newLi.innerHTML = `<li>${todo.text}</li>`
parent.appendChild(newLi);
}
btn.addEventListener('click', (e) => {
e.preventDefault();
let text = input.value;
let todo = {
id: todos.length + 1,
text: text,
complete: false,
}
todos.push(todo);
renderNewToDo(todo);
})
window.onload = () => {
loadTodos();
}

Related

How to auto grow text on input value?

I have an input form field that outputs text on submit to another created input, essentially an editable todo list. I have tried to make the input text value auto grow, but cannot figure out how to do it. Right now the user has to scroll over to see the rest of the text on each list item. This should not be.
What I tried:
I have tried creating a span and attaching editableContent but that makes my input text disappear.
I have tried setting an attribute on max-length on the created input but cannot get it to work. What is the best way to accomplish auto growing the text input value?
Here is the full codepen
const createTodoText = (todo) => {
const itemText = document.createElement("INPUT");
// const itemText = document.createElement("span");
// itemText.contentEditable
// itemText.contentEditable = 'true'
itemText.classList.add("todoText");
itemText.value = todo.name;
itemText.addEventListener("click", (e) => {
e.currentTarget.classList.add("active");
});
// update todo item when user clicks away
itemText.addEventListener("blur", (e) => {
todo.name = e.currentTarget.value;
renderTodos();
});
return itemText;
};
There you go: -
// select DOM elements
const todoForm = document.querySelector(".todo-form");
const addButton = document.querySelector(".add-button");
const input = document.querySelector(".todo-input");
const ul = document.getElementById("todoList");
let todos = [];
todoForm.addEventListener("submit", function (e) {
e.preventDefault();
addTodo(input.value);
});
const addTodo = (input) => {
if (input !== "") {
const todo = {
id: Date.now(),
name: input,
completed: false
};
todos.push(todo);
renderTodos();
todoForm.reset();
}
};
const renderTodos = (todo) => {
ul.innerHTML = "";
todos.forEach((item) => {
let li = document.createElement("LI");
// li.classList.add('item');
li.setAttribute("class", "item");
li.setAttribute("data-key", item.id);
const itemText = createTodoText(item);
const cb = buildCheckbox(item);
const db = buildDeleteButton(item);
// if (item.completed === true) {
// li.classList.add('checked');
// }
li.append(cb);
li.append(db);
li.append(itemText);
ul.append(li);
});
};
const createTodoText = (todo) => {
const itemText = document.createElement("span");
itemText.setAttribute('role','textbox');
itemText.setAttribute('contenteditable',"true");
itemText.classList.add("todoText");
itemText.innerHTML = todo.name;
itemText.addEventListener("click", (e) => {
e.currentTarget.classList.add("active");
});
// update todo item when user clicks away
itemText.addEventListener("blur", (e) => {
todo.name = e.target.textContent;
renderTodos();
});
return itemText;
};
const buildCheckbox = (todo) => {
const cb = document.createElement('input');
cb.type = 'checkbox';
cb.name = 'checkbox';
cb.classList.add('checkbox');
cb.checked = todo.completed;
// checkbox not staying on current state ??
cb.addEventListener('click', function (e) {
if (e.target.type === 'checkbox') {
// todo.completed = e.target.value;
todo.completed = e.currentTarget.checked
e.target.parentElement.classList.toggle('checked');
}
});
return cb;
};
const buildDeleteButton = (todo) => {
const deleteButton = document.createElement("button");
deleteButton.className = "delete-button";
deleteButton.innerText = "x";
deleteButton.addEventListener("click", function (e) {
// duplicates children sometimes ??
const div = this.parentElement;
div.style.display = "none";
todos = todos.filter((item) => item.id !== todo.id);
});
return deleteButton;
};
// //------ Local Storage ------
function addToLocalStorage(todos) {}
function getFromLocalStorage() {}
// getFromLocalStorage();
This is the Javscript code part. In createTodoText, you can see the changes i've made. It's working according to what you want. What i've done is simple used 'span' instead of 'input'.
How about trying something like
if (todo.name.length) {itemText.size = todo.name.length;}

Local storage in JS not loading items problem

I have a problem with the local storage it seems the items are getting saved to local storage but I cannot make it work to load at start.
Any tips and advice much appreciated.
I am posting the code below.
const input = document.getElementById('input');
const list = document.getElementById('list');
const addButton = document.getElementById('addButton');
const completed = document.getElementById("completed");
let LIST;
let id;
let loadSTORAGE = localStorage.getItem("STORAGE");
if (loadSTORAGE) {
LIST = JSON.parse(loadSTORAGE);
id = LIST.length;
loadList(LIST);
} else {
LIST = [];
id = 0;
}
function loadList() {
LIST.forEach(function() {
addTask();
});
}
addButton.addEventListener("click", addTask);
input.addEventListener("keyup", function(event) {
(event.keyCode === 13 ? addTask() : null)
})
function addTask() {
const newTask = document.createElement("li");
const delBtn = document.createElement("button");
const checkBtn = document.createElement("button");
delBtn.innerHTML = "<button>Reset</button>"
checkBtn.innerHTML = "<button>Done</button>"
if (input.value !== "") {
newTask.textContent = input.value;
list.appendChild(newTask);
newTask.appendChild(checkBtn);
newTask.appendChild(delBtn);
LIST.push({
name: input.value,
id: id,
});
id++
input.value = "";
console.log(LIST);
localStorage.setItem("STORAGE", JSON.stringify(LIST));
}
checkBtn.addEventListener("click", function() {
const parent = this.parentNode
parent.remove();
completed.appendChild(parent);
});
delBtn.addEventListener("click", function() {
const parent = this.parentNode
parent.remove();
});
}
You need to break out the logic of building the item and getting the value. Something like the following where the addTask just makes sure there is input and calls a method that builds an item. Now with the localstorage call, you can call just the code that builds the item.
const input = document.getElementById('input');
const list = document.getElementById('list');
const addButton = document.getElementById('addButton');
const completed = document.getElementById("completed");
const loadSTORAGE = localStorage.getItem("STORAGE");
const LIST = loadSTORAGE ? JSON.parse(loadSTORAGE) : [];
let id = LIST.length;
loadList(LIST);
function loadList() {
LIST.forEach(function(data) {
addTaskElement(data);
});
}
function addTask() {
if (input.value !== "") {
cons newItem = {
name: input.value,
id: id,
};
LIST.push(newItem);
id++;
localStorage.setItem("STORAGE", JSON.stringify(LIST));
input.value = "";
addTaskElement(newItem);
}
}
function addTaskElement(data) {
const newTask = document.createElement("li");
const delBtn = document.createElement("button");
const checkBtn = document.createElement("button");
delBtn.textContent = "Reset"
checkBtn.textContent = "Done"
newTask.textContent = data.name;
newTask.appendChild(checkBtn);
newTask.appendChild(delBtn);
list.appendChild(newTask);
}

Todo list wont refresh after pushing to array

Whenever I add a todo to my array, it's not being refreshed in html, what do I need to sort this out? Also, how can I connect the Delete button which is being created in loop to a function?
const form = document.querySelector('form')
const input = document.querySelector('input')
const btnAdd = document.querySelector('button')
let ul = document.querySelector('ul')
let todos = ["one", "two", "three"];
pushTodos = (e) => {
e.preventDefault();
todos.push(input.value);
input.value = '';
console.log(todos);
}
for(let i of todos) {
createList = document.createElement("li");
createList.innerHTML+=i + '<button>Delete</delete>';
console.log(todos);
ul.appendChild(createList);
}
form.addEventListener('submit', pushTodos)
The problem you have is that you only update DOM only once on document load. You don't do anything after updating the list. To fix this, create a sync function to synchronize your memory with the DOM and call it every time you update the list:
let todos = ["one", "two", "three"];
const syncTodosWithDOM = () => {
ul.innerHTML = "";
for (let i of todos) {
createList = document.createElement("li");
createList.innerHTML+=i + '<button>Delete</delete>';
console.log(todos);
ul.appendChild(createList);
}
};
pushTodos = (e) => {
e.preventDefault();
todos.push(input.value);
input.value = '';
console.log(todos);
syncTodosWithDOM();
}
syncTodosWithDOM();
form.addEventListener('submit', pushTodos)
Iterate todos inside pushTodos function. Create a separate function createDomElem which will create dom element and call this function again when the element is pushed in the array
pushTodos = (e) => {
e.preventDefault();
todos.push(input.value);
input.value = '';
console.log(todos);
createDomElem()
}
createDomElem = () => {
for (let i of todos) {
createList = document.createElement("li");
createList.innerHTML += i + '<button>Delete</delete>';
console.log(todos);
ul.innerHTML = ''; // remove previous list
ul.appendChild(createList);
}
}
createDomElem() // will create initial list of todos
In pushTodos you also need to refresh HTML.
pushTodos = (e) => {
e.preventDefault();
todos.push(input.value);
input.value = '';
createList = document.createElement("li");
createList.innerHTML+=input.value + '<button>Delete</delete>';
ul.appendChild(createList);
}

Very new to Javascript OOP: Passing a value to property through input fails to render single Element

I'm having problems rendering individual "li" elements through OOP approach.
I'm fetching the input from the user and using this info to create an item through a class. I'm then connecting this class to the list class responsible for rendering the list.
Once I fetch the value through a click event listener, the singleTaskRendering class isn't working. I wonder if I'm setting this up incorrectly?
const inputAccess = document.querySelector('.control').querySelector('input');
const addItemBtnAccess = document.getElementById('add-item-btn');
const toDoList = [];
const doneList = [];
//ads a li id to the item
const idGenerator = (array) => {
let n = 1;
let message = '';
for (let i = 0; i < array.length; i++) {
n += 1;
}
message = `li-${n}`;
return message;
}
class ItemTask {
constructor(itemValue, idGen) {
this.title = itemValue;
this.id = idGen;
}
}
const addItemBtnHandler = () => {
const toDoList = [];
const inputValue = inputAccess.value;
const item= new ItemTask(inputValue, idGenerator(toDoList));
toDoList.push(item);
return toDoList;
};
class singleTaskRendering {
constructor(product) {
this.product = product;
}
render() {
const titleElement = document.createElement('div');
titleElement.id = this.product.id;
titleElement.innerHTML = `
<li>
<h2>${this.product.title}</h2>
<button>Done</button>
<button>Delete</button>
</li>`;
titleElement.draggable = true;
}
}
class ItemLists {
constructor(listId, items) {
this.items = items;
this.listId = listId;
console.log(this.items, this.listId);
}
renderList() {
const renderHook = document.getElementById('hook');
const createList = document.createElement('lu');
createList.className = 'card';
createList.id = `${this.listId}-list`;
for(let item of this.items) {
const newItem = new singleTaskRendering(item);
console.log(newItem);
const itemEl = newItem.render();
console.log(itemEl, newItem);
createList.apppend(itemEl);
}
renderHook.append(createList);
}
}
const itemList = new ItemLists('active', toDoList);
itemList.renderList();
addItemBtnAccess.addEventListener('click', addItemBtnHandler);
The problem that you are having is that you call ItemLists on page load, which means it will only be processing an empty toDoList.
My solution is to rename renderList to appendItem.
Declare it at the top
Don't pass the list id and list to the constructor instead pass it to
appendItem in the clickhandler.
const inputAccess = document.querySelector('.control').querySelector('input');
const addItemBtnAccess = document.getElementById('add-item-btn');
const itemList = new ItemLists();
const toDoList = [];
const doneList = [];
//ads a li id to the item
const idGenerator = (array) => {
let n = 1;
let message = '';
for (let i = 0; i < array.length; i++) {
n += 1;
}
message = `li-${n}`;
return message;
}
class ItemTask {
constructor(itemValue, idGen) {
this.title = itemValue;
this.id = idGen;
}
}
const addItemBtnHandler = () => {
const toDoList = [];
const inputValue = inputAccess.value;
const item= new ItemTask(inputValue, idGenerator(toDoList));
itemList.appendItem('active', item);
};
class singleTaskRendering {
constructor(product) {
this.product = product;
}
render() {
const titleElement = document.createElement('div');
titleElement.id = this.product.id;
titleElement.innerHTML = `
<li>
<h2>${this.product.title}</h2>
<button>Done</button>
<button>Delete</button>
</li>`;
titleElement.draggable = true;
}
}
class ItemLists {
appendItem(listId, item) {
const renderHook = document.getElementById('hook');
const createList = document.createElement('lu');
createList.className = 'card';
createList.id = `${listId}-list`;
const newItem = new singleTaskRendering(item);
console.log(newItem);
const itemEl = newItem.render();
console.log(itemEl, newItem);
createList.apppend(itemEl);
renderHook.append(createList);
}
}
addItemBtnAccess.addEventListener('click', addItemBtnHandler);

Send Axios.get() Data to the HTML

I'm trying to make Axios.get() Send its Data to the HTML.
I'm Making To-Do app which as you guessed generates Todo list items on the page after entering value of the item in the input field(Also this items are being saved in the MongoDB)
I want to display every item from mongoDB in the style of Todo List items (With edit and delete button)
Any tips please?
let taskInput = document.getElementById("new-task");
let paginationBlock = document.getElementById("pagination");
let addButton = document.getElementsByTagName("button")[0];
addButton.setAttribute("id", "add");
let incompleteTaskHolder = document.getElementById("incomplete-tasks");
let paginationHolder = document.getElementById("pagination");
let listItem;
let label;
let editButton;
let deleteButton;
let editInput;
let checkBox;
let pageCount = 1;
let currentPage = 1;
let deleteCount = 0;
let storeData = [{}]
const setPageCount = () => {
const items = [...incompleteTaskHolder.children];
pageCount = Math.ceil(items.length / 5);
};
setPageCount();
const renderPagination = () => {
const items = [...incompleteTaskHolder.children]
paginationBlock.innerHTML = "";
for (let i = 1; i <= pageCount; i++) {
let pageBtn = document.createElement("button");
pageBtn.id = "pageBtn";
pageBtn.addEventListener("click", () => {
currentPage = i;
paginationDisplay();
});
pageBtn.innerText = i;
paginationBlock.append(pageBtn);
}
};
const paginationLimit = () => {
const items = [...incompleteTaskHolder.children];
if (items.length % 5 === 0) {
items.style.display = "none";
}
};
const paginationDisplay = () => {
const items = [...incompleteTaskHolder.children];
const start = (currentPage - 1) * 5;
const end = start + 5;
items.forEach((item, index) => {
if (index >= start && index < end) {
item.style.display = "block";
} else {
item.style.display = "none";
}
});
};
const sendData = () => {
let getValue = document.getElementById('new-task').value
axios.post('http://localhost:3000/add',{
todo : getValue
}).then(res => {
storeData.push({id : res.data._id})
console.log('res', res);
}).catch(err => {
console.log('err',err);
})
console.log(storeData)
}
const getAll = (data) => {
axios.get('http://localhost:3000').then(res => {
incompleteTaskHolder.innerHTML +=res.data
console.log('res',res.data);
}).catch(err => {
console.log('err',err);
})
}
getAll();
const deleteData = (id) => {
axios.delete(`http://localhost:3000/delete/${id}`,{
id : storeData
}).then(res => {
console.log('res',res)
}).catch(err => {
console.log('err',err)
})
};
let createNewTaskElement = function (taskString) {
listItem = document.createElement("li");
checkBox = document.createElement("input");
label = document.createElement("label");
editButton = document.createElement("button");
deleteButton = document.createElement("button");
editInput = document.createElement("input");
label.innerText = taskString;
checkBox.type = "checkbox";
editInput.type = "text";
editButton.innerText = "Edit";
editButton.className = "edit";
deleteButton.innerText = "Delete";
deleteButton.className = "delete";
listItem.appendChild(checkBox);
listItem.appendChild(label);
listItem.appendChild(editInput);
listItem.appendChild(editButton);
listItem.appendChild(deleteButton);
return listItem;
};
let addTask = function (showData) {
listItem = createNewTaskElement(taskInput.value);
document.getElementById("incomplete-tasks").appendChild(listItem);
bindTaskEvents(listItem, editButton);
setPageCount();
renderPagination();
paginationDisplay();
sendData();
};
let getInput = document.getElementById("new-task");
getInput.addEventListener("keyup", (event) => {
if (event.keyCode === 13) {
event.preventDefault();
document.getElementById("add").click();
}
});
let editTask = function () {
listItem = this.parentNode;
editInput = listItem.querySelector("input[type=text]");
label = listItem.querySelector("label");
containsClass = listItem.classList.contains("editMode");
if (containsClass) {
label.innerText = editInput.value;
} else {
editInput.value = label.innerText;
updateData();
}
listItem.classList.toggle("editMode");
};
let deleteTask = function () {
listItem = this.parentNode;
ul = listItem.parentNode;
ul.removeChild(listItem);
setPageCount();
renderPagination();
paginationDisplay();
deleteData();
};
addButton.onclick = addTask;
let bindTaskEvents = function (taskListItem) {
editButton = taskListItem.querySelector("button.edit");
deleteButton = taskListItem.querySelector("button.delete");
listItem = taskListItem.querySelector("label");
addButton = taskListItem.querySelector("button.add");
listItem.ondblclick = editTask;
editButton.onclick = editTask;
deleteButton.onclick = deleteTask;
};
for (let i = 0; i < incompleteTaskHolder.children.length; i++) {
bindTaskEvents(incompleteTaskHolder.children[i]);
}
Assuming you are getting a list of items as a response like ['todo1', 'todo2' 'todo3']. Let me know if your response is different in structure.
You can create a function that renders your todo list to HTML.
function createTodoList(todoArr) {
const container = document.createElement('div');
const todoList = document.createElement('ul');
document.getElementsByTagName('body')[0].appendChild(container);
container.appendChild(todoList);
todoArr.forEach(function(todo) {
const listItem = document.createElement('li');
listItem.textContent = todo;
todoList.appendChild(listItem);
});
}
// finally call the function with you response.data
createTodoList(res.data)
If you are getting a different response, you might have to

Categories

Resources