Would You Make A Svelte Todo App?

Helitha Rupasinghe - May 17 '22 - - Dev Community

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
Enter fullscreen mode Exit fullscreen mode

...then start Rollup.

npm run dev
Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

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 -->
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .