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))
(action
(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 http://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 ofvar
, and returns the remaining list. Ifflg
is NIL andany
is contained more than once in the value ofvar
, 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"
(fill
'(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"
(fill
'(prog
(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"
'(prog
(addToList (val> (field -3)) (val> (field -2)) (val> (field -1)))
(url *Url) ) )
And now it works!
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.
Sources
- Icons made by Freepik from www.flaticon.com
Top comments (0)