[Simple Web Apps] 3. Building a simple todo list
In this article, let's see how we can implement a basic version of a ToDo list using HTML, CSS, and Typescript.
This is going to be something like the below:
<html>
<head>
<title>Basic To Do application</title>
<meta charset="UTF-8" />
<link href="styles/styles.css" rel="stylesheet" type="text/css" />
<script src="scripts/index.js" async></script>
</head>
<body>
<div id="todoContainer">
<input
type="text"
placeholder="eg.) Pickup Milk"
value=""
id="todoSearch"
class="todo-search"
/>
<div id="itemsContainer"></div>
</div>
</body>
</html>
html,
body {
font-size: 16px;
}
#todoContainer {
display: flex;
flex: 1;
flex-direction: column;
}
#todoContainer #todoSearch {
display: flex;
flex: 1;
padding: 0.75rem;
font-size: 2rem;
outline: none;
}
#todoContainer #itemsContainer {
display: flex;
flex: 1;
margin-top: 0.5rem;
flex-direction: column;
}
.todo-checkbox {
width: 2rem;
}
.todo-item {
display: flex;
padding: 1rem;
background: grey;
margin-bottom: 0.5rem;
}
.todo-title {
font-size: 2rem;
display: inline-flex;
}
.todo-title.done {
text-decoration: line-through;
}
export enum TodoItemStatus {
NotStarted,
Done
}
export interface ITodoItem {
title: string;
}
export interface ITodoItemInternal extends ITodoItem {
id?: string;
dateAdded: number; // For simplicity, we are not considering TimeZones
status?: TodoItemStatus;
}
class TodoList {
container: HTMLElement;
itemsMap: Record<string, ITodoItemInternal> = {};
todoItemsContainer: HTMLDivElement | undefined;
inputElement: HTMLInputElement;
constructor(inputElement: HTMLInputElement, container: HTMLElement) {
this.inputElement = inputElement;
this.container = container;
this.init();
}
async init() {
this.handleEvents();
this.itemsMap = await this.getExistingItems();
}
handleEvents() {
this.inputElement.addEventListener("keyup", (event) => {
if (event.key === "Enter") {
const inputBox: HTMLInputElement = event.target as HTMLInputElement;
this.addItem({
title: inputBox.value
});
inputBox.value = ""; // resets the input box.
}
});
this.container.addEventListener("change", (event: any) => {
if (event.target.classList.contains("todo-checkbox")) {
this.completeItem(event.target.parentNode.id);
}
});
}
async getExistingItems(): Promise<Record<string, ITodoItemInternal>> {
// This is where:
// 1. you can make server calls to get the stored items
// 2. (or) you can get from local storage,
// 3. (or) from any other memory layer
return Promise.resolve({}); // Since, I am using in-memory, this is going to be empty
// on every load.
}
getUniqueIdentifier() {
// Ideally use a uuid library to return the unique identifier
// For simplicity, using Date.now() as the id.
return Date.now();
}
addItem(item: ITodoItem): void {
const id = this.getUniqueIdentifier();
const internalItem = {
...item,
status: TodoItemStatus.NotStarted,
dateAdded: id,
id: id.toString()
};
this.itemsMap[id] = internalItem;
this.renderInView(internalItem);
}
renderInView(internalItem: ITodoItemInternal) {
this.container.appendChild(this.getTodoItemElement(internalItem));
}
getCheckboxItem(checked: boolean = false): HTMLInputElement {
const todoCheckbox: HTMLInputElement = document.createElement("input");
todoCheckbox.type = "checkbox";
todoCheckbox.checked = checked;
todoCheckbox.classList.add("todo-checkbox");
return todoCheckbox;
}
getTitleItem(title: string, checked: boolean = false): HTMLSpanElement {
const todoTitle: HTMLSpanElement = document.createElement("span");
todoTitle.classList.add("todo-title");
todoTitle.innerHTML = title;
if (checked) {
todoTitle.classList.add("done");
}
return todoTitle;
}
getTodoItemElement(internalItem: ITodoItemInternal): HTMLDivElement {
console.log(internalItem);
const todoItem: HTMLDivElement = document.createElement("div");
const checked = internalItem.status === TodoItemStatus.Done;
todoItem.id = internalItem.id as string;
todoItem.classList.add("todo-item");
todoItem.appendChild(this.getCheckboxItem(checked));
todoItem.appendChild(this.getTitleItem(internalItem.title, checked));
return todoItem;
}
completeItem(id: string) {
console.log("Here", id);
this.itemsMap[id].status =
this.itemsMap[id].status === TodoItemStatus.Done
? TodoItemStatus.NotStarted
: TodoItemStatus.Done;
this.refreshView(id);
}
refreshView(id: string) {
(document.getElementById(id) as HTMLDivElement).replaceWith(
this.getTodoItemElement(this.itemsMap[id])
);
}
}
new TodoList(
document.getElementById("todoSearch") as HTMLInputElement,
document.getElementById("itemsContainer") as HTMLDivElement
);
You can run the application live in this codesandbox.
If you like this article, make sure to check out my other articles here. I make sure to post a new article every day. Also, make sure to sign up for the newsletter to directly receive the new articles in your inbox.
Cheers,
Arunkumar Sri Sailapathi.