Thymeleaf | Create dynamic hierarchy trees with Kotlin & Micronaut
This week I had to create a dynamic tree structure with Kotlin and Thymeleaf. I had a dataClass Node, which has children, metadata and a title. These children are also Nodes that themselves and therefore can also have children. The last of these Nodes is called a leaf which contains Metadata. What I needed to do was displaying the whole structure on an HTML page and making it dynamic and clickable, so that only the highest level will be seen at the beginning.
Creating the controller
The controller is one of the two main parts of this application. It is responsible to feed the front-end with the data it needs to work with. Since I work with Micronaut in this project, my controller looks like this:
As you can see, this findDocumentations function will listen on the root address and will use the index view to display the response. Please take note that Root().createWholeTree(metadataRepository.getAll())
will just create the data tree for us to use. This function will just return one parent Node that can contain multiple child nodes.
Consuming data in the Front End
First of all, since we are using Thymeleaf, we should create a new index.html file and add some stuff like the xmlns:th to it.
The most interesting part is probably the list item on line 11. At this place, the first item on the lowest hierarchy level will be displayed. This list item will be replaced by the nestedMenu from the menuFragment.html file. Here it also uses the menuItemsList attribute and sends it the children of the root object, which we sent in the controller.
If we now would refresh the homepage, it would throw an exception, because it can’t find the fragment we wanted to use. To fix that, we need to create it by ourselves.
Defining the menuFragment
The menuFragment.html actually has two different fragments. One of witch is the nestedMenu and the other is just called menu. We have two separate fragments because one time we need a whole unordered list, like when we call it from the index file, and another time we need just the list items themselves.
As you can see, these fragments are contained and called inside of themselves. The menu fragment contains the nestedMenu fragment. The nestedMenu fragment creates a list-item for each menuItem int the menuItemsList, which it gets through its parameter. In this list-item will either be a dynamically generated link to a location or it will display just a title and create a new section that contains a new menu fragment in it.
The way this works is, that only the leaf nodes of the structure contain the metadata. The other nodes only have a title and children. This way we can create as many levels of hierarchy as needed, since we know that only the leaf nodes can contain metadata and everything before needs to call the menu fragment.
This on its own will create a tree that looks something similar to this.
Obviously, this doesn’t really look that beautiful and needs to be styled with some proper css.
With the css added, we can see that only the first level will be shown, just as we wanted it to be. The problem is, that now it is not clickable and we can’t access the links that have a hierarchy above them.
To fix this issue, we need to write a little bit of javascript and add an eventListener for the click event. My final version of this simple script looked like this:
If we put this script into the index.html file, we should be able to click through the hierarchy as expected.
Reflection
What went good
Probably the easiest part of this task was the controller. I got it up and running in few minutes and the Micronaut documentation is very well written and really useful in certain situations. The CSS and JavaScript part was also pretty easy because I found a really good example on W3Schools which I could use more or less directly for my project.
What needs improvement
First I had some trouble creating the fragments with Thymeleaf and understanding that these fragments can call themselves just like you can do with classes in Kotlin. At the beginning I also had some problems consuming the values directly in Thymeleaf. After some time I figured out the right way and since then, pretty much everything worked just fine.