How to create a To-Do App in PicoLisp (Responsive Version) - Part 1

How to create a To-Do App in PicoLisp (Responsive Version) - Part 1


6 min read

In the last post, we showed how to create a To-Do list using the +Chart classes of the PicoLisp GUI framework. It was very easy, but the layout is more or less fixed.

Today, we will create our own app which means that we need to write some functions from scratch. As a little teaser, this is a little demo of how the final result will look like:


We will do it in two steps: First we define the logic, and in the second step the CSS.

Task definition

So what do we actually want? Let's write a little app that displays a list of to-do items. The user can delete each of these items or add a new one. As a little extra-feature, it should be possible to customize the text color.

Some preparation for set-up

As first step, let's create the stub. It's the same like in all previous projects: we need to call (app), (action), and (html).

Instead of (app) let's use again the automatic redirection to a specific port at loading with (and (app) *Port% (redirect (baseHRef) *SesId *Url)).

(setq *Css '("@lib.css" ))

(and (app) *Port% (redirect (baseHRef) *SesId *Url))

   (html 0 "Todo" *Css NIL
      (<h1> "" "Things To Do") ) )

Then we start the server from the terminal by

$ pil @lib/http.l @lib/xhtml.l @lib/form.l  --server 8082 +

You can now point the browser to localhost:8082/todo.l and should see an almost empty tab with title "Todo" and a header "Things To Do".


Prepare a Dummy List

Next, let's start with a little dummy list to create some fake data. We can overtake the data structure from the last example, i. e. a list where each item is a list too. Each list item has the structure ( <item description> <due date> <color> ), for example like this:

(ifn *ToDoList
   (setq *ToDoList '(("shopping" 781423 "green")("cooking" 732842 "red"))) )

Next, we want to access each of these items one by one and print them out. This can be realized by a for-loop:

(form NIL
   (for (I . Item) *ToDoList 
      (<p> NIL (printsp Item))
      (gui '(+Button) "Delete") )

When we put this now inside our html function and re-start the session, we should see something like this:


Of course, the button doesn't have any function yet. Pushing it will simply cause a page reload.

Writing the delete function

Basically, what we want to do is to delete the current Item list element from the *ToDoList once we press the button. Deleting items can be realized using the del function:

(del 'any 'var ['flg]) -> lst

Deletes any from the list in the value of var, and returns the remaining list. If flg is NIL and any is contained more than once in the value of var, only the first occurrence is deleted.

So, basically we could try something like this:

   (for (I . Item) *ToDoList 
      (gui '(+Button) "Delete" '(del Item '*ToDoList) )

However, when we press it, nothing happens! Why?

In our for-loop we are iterating through the list and keep Item as a temporary variable. However, after the build-up is finished, there is no link between Item and the actual list anymore. Therefore the interpreter doesn't "know" which Item we're referring to.

So we need to do it differently - for example, using the fill function: The fill function non-destructively fills a pattern any with its current value. The item to be substituted is marked with an @ symbol. Like this:

(for (I . @Item) *ToDoList
   (<p> NIL (printsp @Item))
   (gui '(+Button) "Delete" 
         '(del '@Item '*ToDoList)))

Note: no quote ' is needed prior to the fill.

Great, now the deletion works! However, when we expand our dummy list to expand it further, we notice that it seems a little bit inconsistent. For example, the last item cannot be deleted because an empty list is re-created due to the (ifn *ToDoList (...)) statement on top of the program. However, when the full list returns, we need to press the "Reload" button to view it properly.

Again, what is happening?

When we POST our form by pressing on the button, all form elements are re-evaluated, but not the complete web page. Therefore some parts remain unchanged and we see inconsistencies. In order to force a full reload, we can use the url function which loads a given URL or filename. We want to reload our current page which we can refer to with the pre-defined global variable *Url.

So, now we have two functions we want to execute with one button press: the del function and the url function. How can we connect them to one statement?

For this we can use the function prog:

(prog . prg) -> any

Executes prg, and returns the result of the last expression.

Applied to our button:

(gui '(+Button) "Delete"
         (del '@Item '*ToDoList)
         (url *Url) ) ) )

Fine, now we can delete items very smoothly. Once the last item is deleted, the whole list re-appears.


Creating the "Adding items" form

So, we have a list now, and we can delete items. So there is only one task left - adding new ones.

Let's open a new form function and define the data input: a +TextField for the item description, a +DateField for the date, and a +RgbPicker for the color. Of course we also need a submit button.

Let's use the +Cue prefix class to pre-populate the field with some hints what kind of input is expected:

      (form NIL
         (gui '(+Cue +TextField) "Thing to do" 30 )
         (gui '(+Cue +DateField) "Enter a date" 10 )
         (gui '(+RgbPicker) )
         (gui '(+Button) "add item") )

The result looks like this:


Appending data

Now the last thing we need to do is to get the form items and add them to the list. We can add the input for each field by relative referencing. For example,

(val> (field -3))

returns the input value of the +TextField because it is located three form elements prior to the submit button. Once we get all items, we need to connect them to a list using the list function:

(list (val> (field -3)) (val> (field -2)) (val> field -1)))

We can append this to a given list using the function queue with following syntax: queue <list> <new item>. To keep the code a little bit more readable, let's extract this to a new function addToList:

(de addToList ( Item Date Color )
   (queue '*ToDoList (list Item Date Color )) )

and modify the button:

(gui '(+Button) "add item" '(addToList (val> (field -3)) (val> (field -2)) (val> field -1)))

Now it almost works. We only observe the same issue like before - the new items are only displayed once the full page is reloaded. So let's apply the same magic as before by connecting the functions with prog:

         (gui '(+Button) "add item"
               (addToList (val> (field -3)) (val> (field -2)) (val> (field -1)))
               (url *Url) ) )

And now it works!


Update 2022-05-11: Instead of '(prog ... (url *Url) ) it is better to call '(post ...), because (url *Url) can cause errors in some specific situations. I have modified the Gitlab source code accordingly. The button source code looks like this:

(gui '(+Button) "add item"
      (addToList (val> (field -3)) (val> (field -2)) (val> (field -1))) ) ) ) ) )

The code for this example up to this point can be downloaded here. The hosted version is here.

It works now, but you couldn't really call it beautiful. In the next post, we will take care about the styling.


Icons made by Freepik from