working on simple to-do-list and I'm stuck with searching tasks on list feature. User is typing in input field and I want to compare it with positions on list, if somethings fits, give it class .active (it's just font-weigth: bold;) and if it doesn't fit anymore, remove the class. For now, it is removing class only when the input field is empty and I have no idea how to edit code so it works as I want. Any ideas? https://github.com/eryk-slowinski/to-do-list
const inputAdd = document.querySelector('div.add input');
const tasksList = document.querySelector('.taskslist');
const addTask = () => {
if (inputAdd.value) {
const li = document.createElement('li');
const deleteButton = document.createElement('button');
li.textContent = inputAdd.value;
deleteButton.textContent = '\xD7';
tasksList.appendChild(li).appendChild(deleteButton);
} else return;
}
tasksList.onclick = (e) => {
if (e.target.tagName !== 'BUTTON') return;
else e.target.parentNode.remove();
}
const searchInList = (e) => {
const searchValue = e.target.value.toLowerCase();
const liList = [...document.querySelectorAll('ul.taskslist li')].filter(li => li.textContent.toLowerCase().includes(searchValue));
if (searchValue === "") liList.forEach(index => index.classList.remove('active'));
else liList.forEach(index => index.classList.add('active'));
}
document.querySelector('div.add button').addEventListener('click', addTask);
document.querySelector('div.search input').addEventListener('input', searchInList);
Right now you only remove .active if searchValue === "". You need to remove .active whenever state changes. You can iterate the items, and decide for each item to remove or add the class:
const searchInList = (e) => {
const searchValue = e.target.value.trim().toLowerCase();
const liList = document.querySelectorAll('ul.taskslist li')
.forEach(li => { // iterate all items
if (searchValue === '' || !li.textContent.toLowerCase().includes(searchValue)) { // if search term is empty or not included in li text
li.classList.remove('active');
} else {
li.classList.add('active');
}
});
}
In this code, you're only removing the class if searchValue is empty.
if (searchValue === "") liList.forEach(index => index.classList.remove('active'));
else liList.forEach(index => index.classList.add('active'));
You want to move this remove() line to the top of the function, and simply remove all active classes each a character is typed into the search box, then add it back in for the new matches each time:
const searchInList = (e) => {
[...document.querySelectorAll('ul.taskslist li')].forEach(li => li.classList.remove('active'));
.....
liList.forEach(index => index.classList.add('active'));
}
const inputAdd = document.querySelector('div.add input');
const tasksList = document.querySelector('.taskslist');
const addTask = () => {
if (inputAdd.value) {
const li = document.createElement('li');
const deleteButton = document.createElement('button');
li.textContent = inputAdd.value;
deleteButton.textContent = '\xD7';
tasksList.appendChild(li).appendChild(deleteButton);
} else return;
}
tasksList.onclick = (e) => {
if (e.target.tagName !== 'BUTTON') return;
else e.target.parentNode.remove();
}
const searchInList = (e) => {
[...document.querySelectorAll('ul.taskslist li')].forEach(li => li.classList.remove('active'));
const searchValue = e.target.value.toLowerCase();
const liList = [...document.querySelectorAll('ul.taskslist li')].filter(li => li.textContent.toLowerCase().includes(searchValue));
liList.forEach(index => index.classList.add('active'));
}
document.querySelector('div.add button').addEventListener('click', addTask);
document.querySelector('div.search input').addEventListener('input', searchInList);
li.active{
color: red;
}
<div class="wrap">
<h1>Things to be done</h1>
<div class="inputs">
<div class="add">
<label>Add task<input type="text"></label>
<button type="submit">Add</button>
</div>
<div class="search">
<label>Search for task<input type="text"></label>
</div>
<div class="counter"></div>
</div>
<div class="list">
<ul class="taskslist">
</ul>
</div>
</div>
Related
This question already has answers here:
javascript show hidden element when input is filled
(4 answers)
Closed last month.
I'm trying to build to-do app and when I add something without typing anything (blank) it still comes with a delete button. I've set it as if you don't type anything in input, then the alert will show as "Please add a task." Here's what I did so far.
window.addEventListener('load', () => {
const taskForm = document.querySelector('#task-form');
const taskInput = document.querySelector('#task-input');
const taskList = document.querySelector('#task-list');
taskForm.addEventListener('submit', (e) => {
e.preventDefault();
const task = taskInput.value;
addTask(task);
taskInput.value = '';
if (!task) {
alert('Please add the task.');
return;
}
});
// Add button
const addTask = (task) => {
const divItem = document.createElement('div');
const showItem = taskList.appendChild(divItem);
showItem.innerHTML = task;
// Delete button
const buttonDelete = document.createElement('button');
buttonDelete.innerHTML = 'Delete';
divItem.appendChild(buttonDelete);
buttonDelete.addEventListener('click', (e) => {
e.preventDefault();
deleteTask(buttonDelete);
});
};
const deleteTask = (buttonDelete) => {
const selectTask = buttonDelete.closest('div');
taskList.removeChild(selectTask);
};
});
<form id="task-form">
<input id="task-input" type="text" placeholder="What's your plans?" />
<input id="task-submit" type="submit" value="Push" />
</form>
<div>
<h1>Tasks</h1>
<div id="task-list"></div>
</div>
You want to check if taskInput.value is blank and then return BEFORE you call the addTask method.
const task = taskInput.value;
if (!task) {
alert('Please add the task.');
return;
}
addTask(task);
taskInput.value = '';
You just need to check if there is no value before adding the task (and not after)
window.addEventListener('load', () => {
const taskForm = document.querySelector('#task-form');
const taskInput = document.querySelector('#task-input');
const taskList = document.querySelector('#task-list');
taskForm.addEventListener('submit', (e) => {
e.preventDefault();
const task = taskInput.value;
if (!task) {
alert('Please add the task.');
return;
}
addTask(task);
taskInput.value = '';
});
// Add button
const addTask = (task) => {
const divItem = document.createElement('div');
const showItem = taskList.appendChild(divItem);
showItem.innerHTML = task;
// Delete button
const buttonDelete = document.createElement('button');
buttonDelete.innerHTML = 'Delete';
divItem.appendChild(buttonDelete);
buttonDelete.addEventListener('click', (e) => {
e.preventDefault();
deleteTask(buttonDelete);
});
};
const deleteTask = (buttonDelete) => {
const selectTask = buttonDelete.closest('div');
taskList.removeChild(selectTask);
};
});
<form id="task-form">
<input id="task-input" type="text" placeholder="What's your plans?" />
<input id="task-submit" type="submit" value="Push" />
</form>
<div>
<h1>Tasks</h1>
<div id="task-list"></div>
</div>
window.addEventListener('load', () => {
const taskForm = document.querySelector('#task-form');
const taskInput = document.querySelector('#task-input');
const taskList = document.querySelector('#task-list');
taskForm.addEventListener('submit', (e) => {
e.preventDefault();
const task = taskInput.value;
addTask(task);
taskInput.value = '';
});
// Add button
const addTask = (task) => {
if (!task || task == "" || task == undefine) {
alert('Please add the task.');
return;
}else{
const divItem = document.createElement('div');
const showItem = taskList.appendChild(divItem);
showItem.innerHTML = task;
// Delete button
const buttonDelete = document.createElement('button');
buttonDelete.innerHTML = 'Delete';
divItem.appendChild(buttonDelete);
buttonDelete.addEventListener('click', (e) => {
e.preventDefault();
deleteTask(buttonDelete);
});
}
};
const deleteTask = (buttonDelete) => {
const selectTask = buttonDelete.closest('div');
taskList.removeChild(selectTask);
};
});
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;}
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();
}
I'm trying to add local storage to my to-do list. While refreshing the page does maintain the list item, the value comes back as undefined. I suspect it's something to do with the lack of an argument when I call the addInput function at the bottom, but I can't see a way around it.
In addition, if the toggled checked class is on and the item is crossed out, is there a way to store the class information?
I'd very much appreciate any help you can give me.
The offending code is below:
https://codepen.io/david-webb/pen/yLeqydK
function saveTodos () {
let jsonstr = JSON.stringify(todos);
localStorage.setItem('todos', jsonstr);
}
function getTodos () {
localStorage.getItem('todoList')
let jsonstr = localStorage.getItem("todos");
todos = JSON.parse(jsonstr);
if (!todos) {
todos = [];
}
}
//cross out text on click
document.addEventListener('click', function(ev) {
if (ev.target.tagName === 'LI') {
ev.target.classList.toggle('checked');
saveTodos ();
}
});
getTodos ();
addInput ();
Try this please:
<input type="text" style="font-size:25px;" id="input" placeholder="Write here">
<button id="addBtn">Add item</button>
<ul id="myUL">
</ul>
<script>
let todo = [];
document.getElementById('addBtn').addEventListener('click', function () {
let value = document.getElementById('input').value;
if (value) {
todo.push(value);
saveTodos()
addInput(value);
}
});
function addInput(text) {
//add list item on click
let listItem = document.createElement('li');
let list = document.getElementById('myUL');
let input = document.getElementById('input').value;
let textNode = document.createTextNode(text);
//create and append remove button
let removeBtn = document.createElement("BUTTON");
list.appendChild(removeBtn);
removeBtn.className = "removeBtn";
removeBtn.innerHTML = "Remove item";
listItem.appendChild(removeBtn);
list.appendChild(listItem);
listItem.appendChild(textNode);
document.getElementById("input").value = "";
removeBtn.addEventListener('click', removeItem);
//console.log(todo);
}
//remove list item on click
function removeItem() {
let item = this.parentNode.parentNode;
let parent = item.parentNode;
let id = parent.id;
let value = parent.innerText;
todo.splice(todo.indexOf(value, 1));
saveTodos();
this.parentNode.parentNode.removeChild(this.parentNode);
console.log(todo)
}
function saveTodos() {
let jsonstr = JSON.stringify(todo);
localStorage.setItem('todos', jsonstr);
}
function getTodos() {
localStorage.getItem('todos')
let jsonstr = localStorage.getItem("todos");
todos = JSON.parse(jsonstr);
if (todos && !todos.length) {
todos = [];
}
else{
if(todos){
for(var intCounter = 0; intCounter < todos.length; intCounter++){
addInput(todos[intCounter]);
}
}
}
}
//cross out text on click
document.addEventListener('click', function (ev) {
if (ev.target.tagName === 'LI') {
ev.target.classList.toggle('checked');
saveTodos();
}
});
getTodos();
// addInput();
</script>
Call addInput within the getTodos function so that as soon as you're done with retreiving the list you print it.
This is what I changed:
function getTodos
function getTodos() {
localStorage.getItem('todos')
let jsonstr = localStorage.getItem("todos");
todos = JSON.parse(jsonstr);
if (todos && !todos.length) {
todos = [];
}
else{
if(todos){
for(var intCounter = 0; intCounter < todos.length; intCounter++){
addInput(todos[intCounter]);
}
}
}
}
Commented addInput().
I am building a mock-up RSVP app, and I can't get the localStorage data to come up when the page refreshes. I am aiming to be able to insert a name and have the name get appended to the invitation list. Then, the user can either repeat those steps for multiple names or edit the names in the list. I have this part down, but if I were to refresh the page, the invitees are no longer in the list below the input bar. I need it to where it will keep the names in the list, and the buttons on the list items (edit, remove) will still work.
With the 'main code' below, the item is added to the localStorage and set as 'rsvp', but the visible list is not updated until I refresh the page. I need it to update every time I hit the submit button. I have tried adding
if (rsvp != null) {
ul.outerHTML = rsvp;
}
right below
console.log(rsvp);
but when I click submit, the list is not updated and in the console you see the data that was loaded the previous time you used the app.
For example, if I type in 'Test', click submit, type in 'Test2', click submit then type in 'Test3' and click submit again - the list is not visibly updated, you get an error in the console saying 'Uncaught DOMException: Failed to set the 'outerHTML' property on 'Element': This element has no parent node.', and the list is never updated until you refresh the page, type in another name and click submit. Again, if you do this, the list is not updated until you repeat the same process.
Main code (without the rsvp 'if' statement in the handler)
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('registrar');
const input = form.querySelector('input');
const mainDiv = document.querySelector('.main');
const ul = document.getElementById('invitedList');
const div = document.createElement('div');
const filterLabel = document.createElement('label');
const filterCheckbox = document.createElement('input');
filterLabel.textContent = "Hide those who haven't responded";
filterCheckbox.type = 'checkbox';
div.appendChild(filterLabel);
div.appendChild(filterCheckbox);
mainDiv.insertBefore(div, ul);
// Creates the list item for the RSVP list
function createLI(text) {
function createElement(elementName, property, value) {
const element = document.createElement(elementName);
element[property] = value;
return element;
}
function appendToLI(elementName, property, value) {
const element = createElement(elementName, property, value);
li.appendChild(element);
return element;
}
const li = document.createElement('li');
appendToLI('span', 'textContent', text);
appendToLI('label','textContent', 'Confirm')
.appendChild(createElement('input', 'type', 'checkbox'));
appendToLI('button', 'textContent', 'edit');
appendToLI('button', 'textContent', 'remove');
return li;
}
form.addEventListener('submit', (e) => {
e.preventDefault();
const text = input.value;
input.value = '';
// Checks for empty string in the input area
if (text === '') {
alert("You have not entered a name, please try again.");
return;
}
// Checks for duplicate names
for (i = 0; i < ul.children.length; i++) {
if (text === ul.children[i].children[0].textContent) {
alert("This name has already been entered. Please enter a different name.");
return;
}
}
const li = createLI(text);
ul.appendChild(li);
localStorage.setItem('rsvp', JSON.stringify(ul.outerHTML));
});
const rsvp = JSON.parse(localStorage.getItem('rsvp'));
if (rsvp != null) {
ul.outerHTML = rsvp;
}
// Changes list item from confirm to confirmed
ul.addEventListener('change', (e) => {
const checkbox = event.target;
const checked = checkbox.checked;
const label = checkbox.parentNode;
const listItem = checkbox.parentNode.parentNode;
if (checked) {
listItem.className = 'responded';
label.childNodes[0].textContent = 'Confirmed';
} else {
listItem.className = '';
label.childNodes[0].textContent = 'Confirm';
}
});
ul.addEventListener('click', (e) => {
if (e.target.tagName === 'BUTTON') {
const button = e.target;
const li = button.parentNode;
const ul = li.parentNode;
const action = button.textContent;
const nameActions = {
remove: () => {
ul.removeChild(li);
},
edit: () => {
const span = li.firstElementChild;
const input = document.createElement('input');
input.type = 'text';
input.value = span.textContent;
li.insertBefore(input, span);
li.removeChild(span);
button.textContent = 'save';
},
save: () => {
const input = li.firstElementChild;
const span = document.createElement('span');
span.textContent = input.value;
li.insertBefore(span, input);
li.removeChild(input);
button.textContent = 'edit';
}
};
// select and run action in button's name
nameActions[action]();
}
});
// Filters out those who have not yet responded
filterCheckbox.addEventListener('change', (e) => {
const isChecked = e.target.checked;
const lis = ul.children;
if (isChecked) {
for (let i = 0; i < lis.length; i++) {
let li = lis[i];
if (li.className === 'responded') {
li.style.display = '';
} else {
li.style.display = 'none';
}
}
} else {
for (let i = 0; i < lis.length; i++) {
let li = lis[i];
li.style.display = '';
}
}
});
});
const rsvp = JSON.parse(localStorage.getItem('rsvp'))
is called within DOMContentLoaded event handler
if (rsvp != null) {
ul.outerHTML = rsvp;
}
is called immediately following .addEventListener(); also the code at Question does not indicate where localStorage.getItem('rsvp') is set before const rsvp = JSON.parse(localStorage.getItem('rsvp')) is called.
You can check is localStorage has the property key "rsvp" before defining rsvp, and use if condition and statement with DOMContentLoaded event handler.