Would You Make A Svelte Todo App?
Helitha Rupasinghe
Posted on May 17, 2022
What Is Svelte?
Svelte is a new javascript tool created by Rich Harris that is easy to learn and use for building fast web applications. However, it's unique compared to other frameworks where it doesn't work with concepts like virtual DOM.
In my opinion the best way to get started is to follow this tutorial and get started with writing Svelte. If you get stuck along the way then fork the SvelteTodoApp hosted on GitHub.
Creating a Svelte Project
We will start this tutorial by initialising our new project with the following command:
# Creating a new project
npx degit sveltejs/template
# Install the dependencies...
npm install
...then start Rollup.
npm run dev
Navigate to localhost:8080. You should see your app running. Edit a component file in src, save it, and reload the page to see your changes.
Adding Bootstrap
I encourage you to add Sveltstrap using one of the following methods:
# Method One: Install sveltestrap and peer dependencies via NPM
npm install sveltestrap
# Method Two: Include CDN within Static HTML layout
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css">
</head>
Folder Structure
On the root level, we will create the following files
- Todo.svelte
- TodoItem.svelte
and modify App.svelte.
Our App Component
To keep things simple we will create the Todo.svelte
and TodoItem.svelte
files with the following structure.
<script>
<!-- JavaScript Logic -->
</script>
<style>
<!-- CSS Styles -->
</style>
<!-- HTML Markup -->
Next, we will edit the App.svelte
file and replace it with the following code to complete the App component.
<script>
import Todo from './Todo.svelte'
</script>
<main>
<Todo />
</main>
Our Todo Component
For this project we will keep this clean and import the TodoItem Component into our scripts section alongside creating some variables for containing our default filter settings: todoText and nextID. Next we will create our sample todo items using the array data structure.
<script>
import TodoItem from './TodoItem.svelte';
let newTodoText = '';
let currentFilter = 'all';
let nextId = 4;
let todoList = [
{
id: 1,
text: 'Write a Svelte Todo App',
completed: true
},
{
id: 2,
text: 'Hit the ↵ button to add a new item.',
completed: false
},
{
id: 3,
text: 'Hit this ❌ to delete an item.',
completed: false
}
];
</script>
Let's start writing the html code for our Todo.svelte
component.
<div class="container-fluid">
<h1>TODO</h1>
<input type="text" class="todo-input" placeholder="New Item ..." bind:value={newTodoText} on:keydown={addTodo} >
{#each filteredtodoList as todo}
<div class="todo-item">
<TodoItem {...todo} on:deleteTodo={handleDeleteTodo} on:toggleComplete={handleToggleComplete} />
</div>
{/each}
<div class="inner-container">
<div><label><input class="inner-container-input" type="checkbox" on:change={checkAlltodoList}>Check All</label></div>
<div>{todoListRemaining} items left</div>
</div>
<div class="inner-container">
<div>
<button on:click={() => updateFilter('all')} class:active="{currentFilter === 'all'}">All</button>
<button on:click={() => updateFilter('active')} class:active="{currentFilter === 'active'}">Active</button>
<button on:click={() => updateFilter('completed')} class:active="{currentFilter === 'completed'}">Completed</button>
</div>
<dir>
<button on:click={clearCompleted}>Clear Completed</button>
</dir>
</div>
</div>
Now we can implement the logic for our functions under our existing items like so.
<script>
function addTodo(event) {
if (event.key === 'Enter') {
todoList = [...todoList, {
id: nextId,
completed: false,
text: newTodoText
}];
nextId = nextId + 1;
newTodoText = '';
}
}
$: todoListRemaining = filteredtodoList.filter(todo => !todo.completed).length;
$: filteredtodoList = currentFilter === 'all' ? todoList : currentFilter === 'completed'
? todoList.filter(todo => todo.completed)
: todoList.filter(todo => !todo.completed)
function checkAlltodoList(event) {
todoList.forEach(todo => todo.completed = event.target.checked);
todoList = todoList;
}
function updateFilter(newFilter) {
currentFilter = newFilter;
}
function clearCompleted() {
todoList = todoList.filter(todo => !todo.completed);
}
function handleDeleteTodo(event) {
todoList = todoList.filter(todo => todo.id !== event.detail.id);
}
function handleToggleComplete(event) {
const todoIndex = todoList.findIndex(todo => todo.id === event.detail.id);
const updatedTodo = { ...todoList[todoIndex], completed: !todoList[todoIndex].completed};
todoList = [
...todoList.slice(0, todoIndex),
updatedTodo,
...todoList.slice(todoIndex+1)
];
}
</script>
Next step is to add the following styles to complete our Todo.Svelte
file.
<style>
h1 {
color: #ff3e00;
text-transform: uppercase;
font-size: 4em;
font-weight: 100;
}
.container-fluid{
max-width: 800px;
margin: 10px auto;
}
.todo-input {
width: 100%;
padding: 10px, 20px;
font-size: 18px;
margin-bottom: 20px;
}
.inner-container {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 16px;
border-top: 1px solid lightgrey;
padding-top: 15px;
margin-bottom: 13px;
}
.inner-container-input {
margin-right: 12px;
}
button {
font-size: 14px;
background-color: white;
appearance: none;
border: none;
}
button:hover {
background: #ff3e00;
color: white;
}
button:focus {
outline: none;
}
.active {
background: #ff3e00;
color: white;
}
</style>
Our TodoItem Component
Let's start by importing createEventDispatcher to be able to create and emit custom events that occur within our Todo App. Next step is to give access to fly from svelte/transitions by adding the following code.
<script>
import { createEventDispatcher } from 'svelte';
import { fly } from 'svelte/transition';
</script>
We now need to make sure that our TodoItem Component is able to recieve the parameters we defined in Todo.Svelte.
<script>
import { createEventDispatcher } from 'svelte';
import { fly } from 'svelte/transition';
export let id;
export let text;
export let completed;
</script>
Next step is to create our dispatch instance and add the logic for the deleteTodo and toggleCompleted Function.
<script>
import { createEventDispatcher } from 'svelte';
import { fly } from 'svelte/transition';
export let id;
export let text;
export let completed;
const dispatch = createEventDispatcher();
function deleteTodo() {
dispatch('deleteTodo', {
id: id
});
}
function toggleCompleted() {
dispatch('toggleCompleted', {
id: id
});
}
</script>
We now have to create the html logic for the TodoItem.Svelte component.
<div class="todo-item">
<div class="todo-item-left" transition:fly="{{ y: 20, duration: 300}}">
<input type = "checkbox" bind:checked={completed} on:change={toggleCompleted}>
<div class="todo-item-label" class:completed={completed}>{text}</div>
</div>
<div class="remove-item" on:click={deleteTodo}>
❌
</div>
</div>
Note 💡 - Even without the css you should now be able to view the sample list data from our Todo.svelte file and add your own items to the Svelte TodoApp.
Cool! We can now implement our styles to our TodoItem component and finish off our project.
<style>
.todo-item {
margin-bottom: 15px;
display: flex;
align-items: center;
justify-content: space-between;
animation-duration: 0.3s;
}
.remove-item {
cursor: pointer;
margin-left: 15px;
}
.remove-item:hover {
color: lightseagreen;
}
.todo-item-left {
display: flex;
align-items: center;
}
.todo-item-label {
border: 1px solid white;
margin-left: 12px;
}
.completed {
text-decoration: line-through;
color: grey;
opacity: 0.4;
}
</style>
Recap
If you followed along then you should have completed the project and finished off your Svelte Todo App.
Now if you made it this far, then I am linking the code to my Sandbox for you to fork or clone and then the job's done.
License: 📝
This project is under the MIT License (MIT). See the LICENSE for more information.
Contributions
Contributions are always welcome...
- Fork the repository
- Improve current program by
- improving functionality
- adding a new feature
- bug fixes
- Push your work and Create a Pull Request
Useful Resources
Posted on May 17, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.