A Todo App With PyScript
Tags: python, javascript, tutorial, discuss
Published: 2022-05-10 17:11:52


*We can run native Python code in the browser using PyScript. In this tutorial we'll build a Todo app.*

## Introduction


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y3tu1f1lp84ljcztcdm1.png)

This walkthrough is based upon the [todo app](https://github.com/pyscript/pyscript/blob/main/pyscriptjs/examples/todo.html) in the examples folder of the [PyScript repository](https://github.com/pyscript/pyscript/).

The app itself is fairly simple:

- An input box to add a new todo along with a button to submit it
- A list of todos
- A checkbox for each todo to mark it as complete

## The boilerplate

Before we can use PyScript in the browser, we must include the PyScript *"runtime"* in our HTML file.

The runtime consists of a JavaScript file which translates Python to the WebAssembly runtime of the browser and a set of standard CSS styles.

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />

    <title>Todo App</title>

    <link rel="icon" type="image/png" href="favicon.png" />
    <link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
    <script defer src="https://pyscript.net/alpha/pyscript.js"></script>
  </head>

  <body class="container">
  </body>
</html>
```

Once these files are loaded, we can use the `<py-script>` HTML tag and include our Python code right there.

## Our Building Blocks


Let's start with building our interface.

First, we create the input box with the submit button at the top of the page.

```html
<div>
  <input id="new-task-content" class="border flex-1 mr-3 border-gray-300 p-2 rounded" type="text">
  <button id="new-task-btn" class="p-2 text-white bg-blue-600 border border-blue-600 rounded" type="submit" pys-onClick="add_task">
    Add task
  </button>
</div>
```

Note that there is barely anything special about it. It's just regular HTML with a few CSS classes (based on TailwindCSS) coming from the `pyscript.css` file we loaded in the boiler plate.

The only special thing here is the binding of the `onClick` event to a function on PyScript:

```html
<button id="new-task-btn" class="[…]" type="submit" pys-onClick="add_task">
```

This `pys-onClick` attribute is usable once we've included the `pyscript.js` file from the boilerplate. Whenever the button is clicked, a function called `add_task` is called in our PyScript code. We will cover that code later.

Next, we need a list of our Todos. So, we create a template for them:


```html
<template id="task-template">
    <section class="task bg-white my-1">
        <label for="flex items-center p-2 ">
            <input class="mr-2" type="checkbox" class="task-check">
            <p class="m-0 inline"></p>
        </label>
    </section>
</template>
```

Anything inside the `<template>` tag is used as a template – obviously. That means that we can re-use that HTML snippet for each todo the user will add. We will see how we can access and use the template in just a second.


## The Python Code

Now, the interesting part happens: We're writing Python code. Inside the browser.

Let's start with accessing elements of the DOM. In PyScript we can use the `Element` class for it. We do not have to import this class as it is added to the global namespace by the PyScript runtime.

```python
task_template = Element("task-template").select(".task", from_content=True)
task_list = Element("list-tasks-container")
new_task_content = Element("new-task-content")
```

We now have access to:
- The input field for new tasks (`new_task_content`)
- The container for the todos (`task_list`)
- The template for a single todo (`task_template`)

Note that the `Element` class takes the `id` attribute of the DOM element to find it.





```python
tasks = []

# define the task template that will be use to render new templates to the page
task_template = Element("task-template").select(".task", from_content=True)
task_list = Element("list-tasks-container")
new_task_content = Element("new-task-content")
```

Remember the `add_task` function we added to our `pys-onClick` attribute on the input field? Now it's time to actually implement this function.

Let's first think about what this function should do:
1. It needs to gather the text from the input field
2. It needs to add this text as a new todo to an existing list of todos
3. It should get the template for a todo, add the content and append the new todo to our list container
4. It should clear the input field once the todo is added


Here is how to do it:

```python
def add_task(*ags, **kws):

    # 1. It needs to gather the text from the input field
    todo_text = new_task_content.element.value
    # ignore empty task
    if not todo_text:
        return None

    # 2. It needs to add this text as a new todo to an existing list of todos
    task_id = f"task-{len(tasks)}"
    task = {
        "id": task_id,
        "content": todo_text,
        "done": False,
        "created_at": dt.now(),
    }

    tasks.append(task)

    # 3. It should get the template for a todo, add the content and append the new todo to our list container
    task_html = task_template.clone(task_id, to=task_list)
    task_html_content = task_html.select("p")
    task_html_content.element.innerText = task["content"]
    task_html_check = task_html.select("input")
    task_list.element.appendChild(task_html.element)

    # 4. It should clear the input field once the todo is added
    new_task_content.clear()
```

Well done! Our todo app is working! We can add todos to our list by just using Python!

## Marking todos as done

Only one thing is missing: We need to be able to mark todos as done.

Remember that little checkbox in front of each todo in our template?

To do so, we just have to add another event listener (just like `pys-onClick`) on the checkbox.

Due to the dynamic nature of the template (we copy the template for each new todo), we cannot use `pys-onClick` inside our template code.
Instead, we must create a new event listener for each new todo item.

So, we can simply add a function which crosses out the todos marked as done for us and bind it to our checkbox element.


```python
def add_task(*ags, **kws):

    # 1. It needs to gather the text from the input field
    todo_text = new_task_content.element.value
    # ignore empty task
    if not todo_text:
        return None

    # 2. It needs to add this text as a new todo to an existing list of todos
    task_id = f"task-{len(tasks)}"
    task = {
        "id": task_id,
        "content": todo_text,
        "done": False,
        "created_at": dt.now(),
    }

    tasks.append(task)

    # 3. It should get the template for a todo, add the content and append the new todo to our list container
    task_html = task_template.clone(task_id, to=task_list)
    task_html_content = task_html.select("p")
    task_html_content.element.innerText = task["content"]
    task_html_check = task_html.select("input")
    task_list.element.appendChild(task_html.element)

    # 4. It should clear the input field once the todo is added
    new_task_content.clear()

    # Create an event listener for our todo
    def check_task(evt=None):
        task["done"] = not task["done"]
        if task["done"]:
            task_html_content.element.classList.add("line-through")
        else:
            task_html_content.element.classList.remove("line-through")
    # bind the new event listener to our checkbox
    task_html_check.element.onclick = check_task
```

## Try it yourself

You can try out the [`todo.html`](https://bas.codes/media/pyscript/todo.html) file yourself. When you click on `View Source` in your browser, you will get the whole file to play around.


What do you think about PyScript? Will it play a role on the JS world? What would you like to build with it?