Guide: Todo list app

Register on Lenra

App example : basic Todo List

Now that you know the basics of a lenra app, we can create our first app : a basic TODO List.

Note : To follow this guide, you must have basic knowledge about javascript, Mongo query language and API call (using axios).

First of all, we want to organize our model (database).

We will have only one collection : the task collection that will store all of our tasks.

A task item will contain 4 properties :

Our document will look like that :

{
    "_id": "ObjectId(634d692fcbd9f6704818309c)"
    "description": "Code my first app with Lenra",
    "done": false,
    "user": "a ref to the user"
}

A simple Mongo document

Create a new task

Now that we know what a task looks like, we want to create a new view to let the user create tasks. To do that, we will create a new file in the views directory : addTaskForm.js

Before anything else, we will register this view to our application. This will link this function to the addTaskForm ****action. To do this, go to the index.js file and add a line in the views object.

module.exports = async () => {
  return {
    views: {
      main: require('./views/main'),
      ...
      addTaskForm: require('./views/addTaskForm'),
    },
    listeners: {
      ...
    },
    rootview: 'main'
  }
}

index.js

In the addTaskForm.js view, we create a new function (and export it). In this function, we will first add a form component.

module.exports = (data, props) => {
  return {
    type: "form",
        onSubmit: {
      action: "submitTask"
    },
    child: {...}
  }
}

views/addTaskForm.js

In this view, we define the onSubmit listener. This tells the UI to submit the submitTask action when the form is submitted.

Then we will declare the inputs in our form.

module.exports = (data, props) => {
  return {
    type: "form",
        onSubmit: {
      action: "submitTask"
    },
    child: [
        // 1 - Flex component
        {
      type: "flex",
      crossAxisAlignment: "center",
            spacing: 2,
      children: [
              // 2 - label
                {
            type: "text",
            value: "Your task : "
            },
                // 3 - The textfield to type the description
            {
            type: "flexible",
            child: {
              type: "textfield",
              value: "",
              name: "description"
            }
            },
                // 4 - The button to submit the form
                {
            type: "button",
            text: "Add",
            submit: true,
            }
            ]
    }
  }
}

views/addTaskForm.js

Let's see what happens here :

1 : The Flex component

The form allows only one child. The reason is simple, it does not infer how you want to place your inputs in the form. That is why we add a Flex component that describes how the children will be placed in the UI.

By default, the flex is horizontal. We add the crossAxisAlignment: "center" to vertically center the children and a spacing: 2 to add some spaces between the children.

2 : The label

This one simply adds a label before the textfield. That’s it !

3 : Textfield

This is a two in one.

The textfield component simply adds a textfield with a default value to empty (value: “”). The name: “description” connects this textfield first form up in the component tree. This way, when the form will be submitted, the event will contain a description field that contains the value of the textfield :

{
    "value": {
        "description": "valueOfTheTextfield"
    }
}

The event object

Then we have the flexible component. that allows the textfield to take all the remaining space in the flex above. As simple as that !

4 : The submit button

This component is a simple button in which we add the submit: true property. This connects the form to this button. This way, when the button is pressed, the form will be automatically submitted (the onSubmit listener will be called).

Call the form

Now that our form is ready, we just have to call it in our main.js component.

module.exports = (data, props) => {
    return {
        type: "flex",
        direction: "vertical",
        crossAxisAlignment: "center",
        children: [
            {
                type: "text",
                value: "Lenra Todo List",
                style: {
                    fontWeight: "w800",
                    fontSize: 22
                }
            },
            {
                type: "view",
                name: "addTaskForm",
            }
        ]
    }
}

views/main.js

Now you can start your app in your terminal using

lenra dev

Then open your browser at http://localhost:4000

Your app should look like that.

You should be able to type some text in the textfield. But for now the “add” button does nothing. Let's change that !

Create the submitTask listener

We will now create the listener that will react to the “add” button pressed.

Remember the action: "submitTask" property in the form onSubmit listener ? That’s the name of our listener. So create a submitTask.js file in the listeners directory and add this code to it :

// 1 - import the api service
const apiService = require("./api")

// 2 : create the function with "props", "event" and "api" arguments
module.exports = async (props, event, api) => {
        // 3 - Call the service to create a new task document.
    return apiService.createDoc(api, "tasks", {
        description: event.value.description,
        done: false,
        user: "@me"
    });
}

listeners/submitTask.js

1 : Import the API service

This API service already exists in your base template by default. It simply defines a bunch of useful functions to easily call the data API. In this listener, we will use the “createDoc” function. You can check the content of this apiService.

2 : Create the function

A listener function takes 3 parameters : props, event and api.

In this function body we can call our Data API to create our new task.

Keep in mind that the listener must execute relatively fast in order to avoid long loader. (less than a second ideally).

3 : Create our task

This is where the magic starts. The apiService.createDoc function will create a new document in the tasks collection.

Remember our database model. The user field should contain the current user ID to be able to filter the task that belongs to the user. To do this, Lenra offers a shortcut to access contextual data. In our example @me is a reference to the current user ID and will be replaced automatically.

When the document is added, the UI will be rebuilt in order to instantly display the updated interface to the user.

Remember to register this listener to the index.js manifest.

module.exports = async () => {
  return {
    ...
    listeners: {
            ...
      submitTask: require('./listeners/submitTask'),
    },
    ...
  }
}

index.js

You can restart the app, run lenra dev again and you should be able to create a new task now !

But wait… The task are still not visible yet.

List the user tasks

Now that we can add new tasks in our database, let's list them in the interface.

To do this, create a new taskList.js view and register it to the manifest index.js.

module.exports = (data, props) => {

    return {
        type: "flexible",
        child: {
            type: "flex",
            direction: "vertical",
            scroll: true,
            children: taskList(data),
        }
    }
}

function taskList(tasks) {
    if (tasks == undefined || tasks.length <= 0) {
        return [{
            type: "text",
            value: "No tasks"
        }]
    }

    return tasks.map(task => {
        return {
            type: "view",
            name: "taskCard",
            props: {
                task: task
            }
        }
    })
}

views/taskList.js

Nothing too crazy here. We simply loop through the tasks list and call the taskCard view with the task in the properties. If there is no task in the data, simply print a message.

I’m sure you will be able to create the taskCard view by yourself ! If you have some trouble, check the example in our github project. (Don’t forget to register the taskCard too !)

Now we want to call our taskList with some data in it. To do this, go to the main.js view.

module.exports = (data, props) => {
    return {
        type: "flex",
        direction: "vertical",
        crossAxisAlignment: "center",
        children: [
            {
                type: "text",
                value: "Lenra Todo List",
                style: {
                    fontWeight: "w800",
                    fontSize: 22
                }
            },
            // Call the taskList view with some data.
            {
                type: "view",
                name: "taskList",
                coll: "tasks",
                query: {
                    "user": "@me"
                }
            },
            {
                type: "view",
                name: "addTaskForm",
            }
        ]
    }
}

views/main.js

As you can see, we use the same component that we used to call the addTaskForm view. The only difference is the coll and query properties.

The coll property defines the collection where we want to run the query.

Then the query is a simple mongo query with the lenra specificity : the @me to reference the current user. It is the same trick we used to create our task before. This query will filter the tasks collection to give us only the task associated with the current user. The result of this query is the data argument in our view function.

And we’re done ! Restart the lenra CLI (lenra dev). The task list should now be visible !

Add some features

We now have a simple list. But a todo list is more than just a list !

With your new knowledge, you should be able to create the next features.

Delete a task

Add a button to delete the task next to it. When the user clicks this button, remove the task from the database.

You will need :

Toggle the tasks

When the user clicks on a task, toggle thedone boolean. Then in the TaskCard, depending of this boolean, cross the description of the task.

You will need :

Troubleshooting

Can I have some help anywhere ?

If you need help, you can check our todo app github repo where all the above feature are implemented.

You also can ask questions using the Help wanted category of our github discussion tab.

I have created my listener/view but it says that this listener/view does not exists.

Be sure to register your listener/view in the index.js first ! This tells your javascript server to call your function when the listener action/view name is called.