Simple JavaScript TodoList
by Ayo Isaiah
HTML
<main>
<div class="container">
<h1 class="app-title">todos</h1>
<ul class="todo-list js-todo-list"></ul>
<!-- add the empty state here -->
<div class="empty-state">
<svg class="checklist-icon"><use href="#checklist-icon"></use></svg>
<h2 class="empty-state__title">Add your first todo</h2>
<p class="empty-state__description">What do you want to get done today?</p>
</div>
<!-- end -->
<form class="js-form">
<input autofocus type="text" aria-label="Enter a new todo item" placeholder="E.g. Build a web app" class="js-todo-input">
</form>
</div>
</main>
<svg>
<defs>
<symbol id="checklist-icon" viewBox="-52 0 512 512"><path d="m217.140625 47.226562c0 7.1875-5.832031 13.015626-13.019531 13.015626-7.191406 0-13.019532-5.828126-13.019532-13.015626 0-7.191406 5.828126-13.019531 13.019532-13.019531 7.1875 0 13.019531 5.828125 13.019531 13.019531zm0 0"/><path d="m140.917969 98.820312c4.144531 0 7.5-3.355468 7.5-7.5v-11.4375c0-4.140624-3.355469-7.5-7.5-7.5-4.140625 0-7.5 3.359376-7.5 7.5v11.4375c0 4.144532 3.359375 7.5 7.5 7.5zm0 0"/><path d="m267.320312 98.820312c4.144532 0 7.5-3.355468 7.5-7.5v-11.4375c0-4.140624-3.355468-7.5-7.5-7.5-4.140624 0-7.5 3.359376-7.5 7.5v11.4375c0 4.144532 3.359376 7.5 7.5 7.5zm0 0"/><path d="m204.121094 104.925781c8.242187 0 15.984375-3.449219 21.246094-9.464843 2.726562-3.117188 2.40625-7.855469-.710938-10.582032-3.121094-2.726562-7.855469-2.410156-10.582031.710938-2.410157 2.753906-6.039063 4.335937-9.953125 4.335937-3.917969 0-7.542969-1.582031-9.953125-4.335937-2.726563-3.121094-7.464844-3.4375-10.582031-.710938-3.121094 2.726563-3.4375 7.464844-.710938 10.582032 5.257812 6.015624 13 9.464843 21.246094 9.464843zm0 0"/><path d="m162.492188 184.390625c-1.308594-3.929687-5.558594-6.054687-9.484376-4.742187-21.402343 7.132812-37.015624 17.507812-46.878906 25.582031v-9.789063c0-4.140625-3.359375-7.5-7.5-7.5s-7.5 3.359375-7.5 7.5v12.710938c0 11.421875 13.816406 17.8125 22.5...
CSS
html {
box-sizing: border-box;
}
*, *::before, *::after {
box-sizing: inherit;
margin: 0;
padding: 0;
}
body {
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans,
Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;
line-height: 1.4;
}
.container {
width: 100%;
max-width: 500px;
margin: 0 auto;
padding-left: 10px;
padding-right: 10px;
color: #333;
height: 90vh;
margin-top: 5vh;
margin-bottom: 5vh;
overflow-y: auto;
}
.app-title {
text-align: center;
margin-bottom: 20px;
font-size: 80px;
opacity: 0.5;
}
svg {
width: 64px;
height: 64px;
}
.todo-list {
list-style: none;
margin-bottom: 20px;
}
.todo-item {
margin-bottom: 10px;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.todo-item span {
flex-grow: 1;
margin-left: 10px;
margin-right: 10px;
font-size: 22px;
}
.done span {
text-decoration: line-through;
}
input[type="checkbox"] {
display: none;
}
.tick {
width: 30px;
height: 30px;
border: 3px solid #333;
border-radius: 50%;
display: inline-flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.tick::before {
content: '✓';
font-size: 20px;
display: none;
}
.done .tick::before {
display: inline;
}
.delete-todo {
border: none;
background-color: transparent;
outline: none;
cursor: pointer;
}
.delete-todo svg {
width: 30px;
height: 30px;
pointer-events: none;
}
form {
width: 100%;
display: flex;
justify-content: space-between;
}
input[type="text"] {
width: 100%;
padding: 10px;
border-radius: 4px;
border: 3px solid #333;
}
/* Add this below all the other styles */
.empty-state {
flex-direction: column;
align-items: center;
justify-content: center;
display: none;
}
.checklist-icon {
margin-bottom: 20px;
}
.empty-state__title, .empty-state__description {
margin-bottom: 20px;
}
/* Add this below the other styles */
.todo-list:empty {
display:...
JavaScript
let todoItems = [];
function renderTodo(todo) {
localStorage.setItem('todoItems', JSON.stringify(todoItems));
const list = document.querySelector('.js-todo-list');
const item = document.querySelector(`[data-key='${todo.id}']`);
if (todo.deleted) {
item.remove();
if (todoItems.length === 0) list.innerHTML = '';
return
}
const isChecked = todo.checked ? 'done': '';
const node = document.createElement("li");
node.setAttribute('class', `todo-item ${isChecked}`);
node.setAttribute('data-key', todo.id);
node.innerHTML = `
<input id="${todo.id}" type="checkbox"/>
<label for="${todo.id}" class="tick js-tick"></label>
<span>${todo.text}</span>
<button class="delete-todo js-delete-todo">
<svg><use href="#delete-icon"></use></svg>
</button>
`;
if (item) {
list.replaceChild(node, item);
} else {
list.append(node);
}
}
function addTodo(text) {
const todo = {
text,
checked: false,
id: Date.now(),
};
todoItems.push(todo);
renderTodo(todo);
}
function toggleDone(key) {
const index = todoItems.findIndex(item => item.id === Number(key));
todoItems[index].checked = !todoItems[index].checked;
renderTodo(todoItems[index]);
}
function deleteTodo(key) {
const index = todoItems.findIndex(item => item.id === Number(key));
const todo = {
deleted: true,
...todoItems[index]
};
todoItems = todoItems.filter(item => item.id !== Number(key));
renderTodo(todo);
}
const form = document.querySelector('.js-form');
form.addEventListener('submit', event => {
event.preventDefault();
const input = document.querySelector('.js-todo-input');
const text = input.value.trim();
if (text !== '') {
addTodo(text);
input.value = '';
input.focus();
}
});
const list = document.querySelector('.js-todo-list');
list.addEventListener('click', event => {
if (event.target.classList.contains('js-tick')) {
const itemKey = event.target.parentElement.dataset.key;
...