I am learning JavaScript and this is a simple todo application I made which uses local storage. It is currently running at: https://koraytugay.github.io/vanillatodo/#. The code is as follows. I am open to any suggestions on how the structure / architecture or implementation details should/can be improved. Can be cloned from: https://github.com/koraytugay/vanillatodo and it looks like this:
index.html
<!DOCTYPE html>
<html>
<head>
<title>VanillaTodo</title>
<link rel="stylesheet" href="style.css"/>
</head>
<body>
<div id="container">
<input id="new-todo-input" type="text">
<h2>To Do Items</h2>
<div id="todo-items"></div>
<hr/>
<a href="#" id="delete-all-todo-items">Delete All</a>
</div>
<script type="module" src="src/todoController.js"></script>
</body>
</html>
todoItemStorageService.js
export default (function() {
const todoItems = 'todo-items';
const idCounter = 'id-counter';
function getTodoItems() {
if (localStorage.getItem(todoItems) === null) {
localStorage.setItem(todoItems, JSON.stringify([]));
localStorage.setItem(idCounter, '1');
}
return JSON.parse(localStorage.getItem(todoItems));
}
function getNextTodoItemId() {
const nextTodoItemId = parseInt(localStorage.getItem(idCounter));
localStorage.setItem(idCounter, '' + (nextTodoItemId + 1));
return nextTodoItemId;
}
function setTodoItems(items) {
localStorage.setItem(todoItems, JSON.stringify(items));
}
function deleteTodoItem(id) {
setTodoItems(getTodoItems().filter(todoItem => todoItem.id !== id));
}
function deleteTodoItems() {
setTodoItems([]);
}
function addTodoItem(todoItem) {
setTodoItems([...getTodoItems(), todoItem]);
}
function getTodoItem(id) {
return getTodoItems().filter(todoItem => todoItem.id === id)[0];
}
function updateTodoItem(todoItem) {
const allTodoItems = getTodoItems();
const itemIndex = allTodoItems.findIndex(item => item.id === todoItem.id);
allTodoItems[itemIndex].text = todoItem.text;
allTodoItems[itemIndex].id = todoItem.id;
allTodoItems[itemIndex].completed = todoItem.completed;
setTodoItems(allTodoItems);
}
return {
getNextTodoItemId,
addTodoItem,
getTodoItems,
getTodoItem,
updateTodoItem,
deleteTodoItem,
deleteTodoItems
}
}());
todoService.js
import todoItemsStorageService from './todoItemsStorageService.js';
export default (function() {
function Todo(text) {
this.text = text;
this.id = `todo-item-${todoItemsStorageService.getNextTodoItemId()}`;
this.completed = false;
todoItemsStorageService.addTodoItem(this);
}
function toggleTodoItemCompletedStatus(id) {
const todoItem = todoItemsStorageService.getTodoItem(id);
todoItem.completed = !todoItem.completed;
todoItemsStorageService.updateTodoItem(todoItem);
}
const getTodoItems = () => todoItemsStorageService.getTodoItems();
const deleteTodoItem = (id) => todoItemsStorageService.deleteTodoItem(id);
const deleteTodoItems = () => todoItemsStorageService.deleteTodoItems();
return {
Todo,
getTodoItems,
toggleTodoItemCompletedStatus,
deleteTodoItem,
deleteTodoItems
};
}());
todoController.js
(function() {
const findById = id => document.querySelector(`#${id}`);
const createElement = (tag, options) => document.createElement(tag, options);
window.addEventListener('load', function() {
refreshUi();
});
findById('new-todo-input').addEventListener('keydown', e => {
if ('Enter' === e.key && e.target.value) {
new todoService.Todo(e.target.value);
e.target.value = '';
refreshUi();
}
});
findById('delete-all-todo-items').addEventListener('click', e => {
todoService.deleteTodoItems();
refreshUi();
});
function toggleTodoCompleteStatus(id) {
todoService.toggleTodoItemCompletedStatus(id);
refreshUi();
}
function deleteTodoItem(id) {
todoService.deleteTodoItem(id);
refreshUi();
}
function createTodoDiv(todoItem) {
const todoDiv = createElement('div');
todoDiv.id = todoItem.id;
const markAsDoneCheckbox = createElement('input');
markAsDoneCheckbox.setAttribute('type', 'checkbox');
markAsDoneCheckbox.addEventListener('click', () => toggleTodoCompleteStatus(todoItem.id));
const todoSpan = createElement('span');
todoSpan.innerText = todoItem.text;
const deleteTodoItemLink = createElement('a');
deleteTodoItemLink.setAttribute('href', '#');
deleteTodoItemLink.addEventListener('click', () => deleteTodoItem(todoItem.id));
deleteTodoItemLink.innerText = '❌';
if (todoItem.completed) {
todoSpan.classList.add('done');
markAsDoneCheckbox.setAttribute('checked', 'checked');
}
todoDiv.appendChild(markAsDoneCheckbox);
todoDiv.appendChild(todoSpan);
todoDiv.appendChild(deleteTodoItemLink);
return todoDiv;
}
function refreshUi() {
const todoItemsDiv = findById('todo-items');
todoItemsDiv.innerHTML = '';
todoService.getTodoItems().forEach(todoItem => todoItemsDiv.appendChild(createTodoDiv(todoItem)));
}
})();
