Creating a User Interface to the Database: Displaying the data

Creating a User Interface to the Database: Displaying the data


4 min read

In the last post, we have set up a small script that starts the database and the server and allows us access to a specific record via the command line.

Now let's expand this to a recursive view on all connected items and make it clickable. In the end, it will look like this:


Accessing the data

As we could see already in our proof of concept-example, we can access the current record via (This) and the : functions. We could use this to define the headline of our tree: "Family tree of X Y (DD-MM-YYYY to MM-DD-YYYY)". In case there is no year available, let's print <unknown date>.

So we have a mix of input parameters, mostly strings and database values. On top, we have some characters that are usually reserved, such as < in <unknown date>. How can we handle this?

For these kind of purposes, we can use the functions of the compiled library which provides formatting functions that are faster than the uncompiled standard lisp functions. ht:Prin replaces reserved characters by their HTML-counterpart such as &lt;. If you're dealing with user input, you should never use the standard prin functions, because it leaves all doors open for cross-side scripting attacks! (We will come back to that point in a later post!)

   "Family tree of "
   (: nm) 
   " ("
   (or (datStr (: dat)) "<unknown date>")
   " to "
   (or (datStr (: fin)) "<unknown date>" )
   ") " ) )

Let's pass this as title to our page. With a little bit of bootstrap, we get this:


Next, let's print out the person and his/her mate (if available) in one line of the list, and their kids on subsequent, indented lines. It should look like this:

  • Person A -- Partner (optional)

    • Child 1 of A

    • Child 2 of A

    • ...

We can do this by starting an underordered list <ul>. The first list item <li> is pretty straightforward:

(<ul> NIL
   (<li> NIL 
      (ht:Prin (: nm))
      (when (: mate)
         (prin " -- ")
         (ht:Prin (: mate nm)) ) )

Note: we only need ht:Prin if we suspect some reserved characters inside. Let's keep it for all database records, just to be on the safe side.

Next, let's print the person's kids. In order to create the indentation, we need to put them in another unordered list <ul> within the current one. So we define another <li> and get the list of kids with (: kids). Then we loop through the list and print each of them on a new line.

   (<li> NIL
      (when (: kids)
         (<ul> NIL (for This @
            (<li> NIL (ht:Prin (: nm))) ) ) ) )

@ is the result of the last evaluation, in this case the current kid.

With help of some Bootstrap classes, this is how it looks like:


Making it recursive

Obviously, with this approach we have to specify each layer manually. If we want to add the grandchildren, we need to write another loop, and so on. So this is a perfect case for anonymous recursion.

We can define an anonymous recursion using the recur/recurse functions:

(recur fun) -> any
(recurse ..) -> any
Implements anonymous recursion, by defining the function recurse on the fly. During the execution of fun, the symbol recurse is bound to the function definition fun.

The recursion level should just start right after we opened the first <ul> tag and be executed for each child. The only argument is ( This ).

(<ul> NIL
   (recur (This)

So, instead of (<ul> NIL (for (: kids), we recurse each list element of (: kids) using mapc:

 (<ul> NIL (mapc recurse (: kids)))

This is the full printout function including the bootstrap classes:

(<ul> "list-group list-group-flush"
   (recur (This)
      (<li> "list-group-item "
         (ht:Prin (: nm))
         (when (: mate)
            (prin " -- ")
            (ht:Prin (: mate nm)) ) )
      (when (: kids)
         (<ul> NIL (mapc recurse (: kids))) ) ) )

Now we get the output recursively:


Well, the next thing you would probably expect is that you can click on each name to expand them in their own page. Links can be created using the <href> function, syntax:

( <href>  #{displayed text} #{relative or absolute path} )

In this case, we only need a relative path, because the base-URL will stay the same. In general, any path arguments are appended to the base-URL after a question mark ?. In order to encode our database object the way we need it, we can use another function from the ht-library, ht:Fmt. So instead of the name, we display a link:

(<href> (: nm) (pack "?" (ht:Fmt This)))

and for the partner:

(<href> (: mate nm) (pack "?" (ht:Fmt (: mate))))

And that's all! Now we've created a clickable, recursive view on the database items.


The complete source code for this example can be found here.

In the next post, we will see how we can generate a JSON list out of this data that can be shared via a REST API.